-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MessagePack timestamp type #168
Changes from 14 commits
44a2df3
95361d2
0917458
67c121e
b2c5f65
d4e938b
a0398ca
d31e669
54ae833
72ba094
37eb00e
7d16656
f56ae5e
b59534e
733ed14
f49a8ff
036f9b4
7566db5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# frozen_string_literal: true | ||
|
||
# MessagePack extention packer and unpacker for built-in Time class | ||
module MessagePack | ||
module Time | ||
# 3-arg Time.at is available Ruby >= 2.5 | ||
TIME_AT_3_AVAILABLE = begin | ||
!!::Time.at(0, 0, :nanosecond) | ||
rescue ArgumentError | ||
false | ||
end | ||
|
||
Unpacker = if TIME_AT_3_AVAILABLE | ||
lambda do |payload| | ||
tv = MessagePack::Timestamp.from_msgpack_ext(payload) | ||
::Time.at(tv.sec, tv.nsec, :nanosecond) | ||
end | ||
else | ||
lambda do |payload| | ||
tv = MessagePack::Timestamp.from_msgpack_ext(payload) | ||
::Time.at(tv.sec, tv.nsec / 1000.0) | ||
end | ||
end | ||
|
||
Packer = lambda { |time| | ||
MessagePack::Timestamp.to_msgpack_ext(time.tv_sec, time.tv_nsec) | ||
} | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# frozen_string_literal: true | ||
|
||
module MessagePack | ||
Timestamp = Struct.new(:sec, :nsec) | ||
|
||
class Timestamp # a.k.a. "TimeSpec" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That looks non-standard way to add methods to the class from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't find how to use the NG(1):
NG(2):
NG(3; it's not what I want):
I'm sorry I'm not good at Ruby 😭 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean that there is no way to refer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I use Struct because of its auto-generated But yes, I'll use a normal class; it's just easy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done: 733ed14 |
||
# Because the byte-order of MessagePack is big-endian in, | ||
# pack() and unpack() specifies ">". | ||
# See https://docs.ruby-lang.org/en/trunk/Array.html#method-i-pack for details. | ||
|
||
# The timestamp extension type defined in the MessagePack spec. | ||
# See https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type for details. | ||
TYPE = -1 | ||
|
||
TIMESTAMP32_MAX_SEC = (1 << 32) - 1 | ||
TIMESTAMP64_MAX_SEC = (1 << 34) - 1 | ||
|
||
def self.from_msgpack_ext(data) | ||
case data.length | ||
when 4 | ||
# timestamp32 (sec: uint32be) | ||
sec, = data.unpack('L>') | ||
new(sec, 0) | ||
when 8 | ||
# timestamp64 (nsec: uint30be, sec: uint34be) | ||
n, s = data.unpack('L>2') | ||
sec = ((n & 0b11) << 32) | s | ||
nsec = n >> 2 | ||
new(sec, nsec) | ||
when 12 | ||
# timestam96 (nsec: uint32be, sec: int64be) | ||
nsec, sec = data.unpack('L>q>') | ||
new(sec, nsec) | ||
else | ||
raise "Invalid timestamp data size: #{data.length}" | ||
end | ||
end | ||
|
||
def self.to_msgpack_ext(sec, nsec) | ||
if sec >= 0 && nsec >= 0 && sec <= TIMESTAMP64_MAX_SEC | ||
if nsec === 0 && sec <= TIMESTAMP32_MAX_SEC | ||
# timestamp32 = (sec: uint32be) | ||
[sec].pack('L>') | ||
else | ||
# timestamp64 (nsec: uint30be, sec: uint34be) | ||
nsec30 = nsec << 2 | ||
sec_high2 = sec << 32 # high 2 bits (`x & 0b11` is redandunt) | ||
sec_low32 = sec & 0xffffffff # low 32 bits | ||
[nsec30 | sec_high2, sec_low32].pack('L>2') | ||
end | ||
else | ||
# timestamp96 (nsec: uint32be, sec: int64be) | ||
[nsec, sec].pack('L>q>') | ||
end | ||
end | ||
|
||
def to_msgpack_ext | ||
self.class.to_msgpack_ext(sec, nsec) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'spec_helper' | ||
|
||
describe MessagePack::Timestamp do | ||
describe 'register_type with Time' do | ||
let(:factory) do | ||
factory = MessagePack::Factory.new | ||
factory.register_type( | ||
MessagePack::Timestamp::TYPE, | ||
Time, | ||
packer: MessagePack::Time::Packer, | ||
unpacker: MessagePack::Time::Unpacker | ||
) | ||
factory | ||
end | ||
|
||
let(:time) { Time.local(2019, 6, 17, 1, 2, 3, 123_456) } | ||
it 'serializes and deserializes Time' do | ||
packed = factory.pack(time) | ||
unpacked = factory.unpack(packed) | ||
expect(unpacked).to eq(time) | ||
end | ||
end | ||
|
||
describe 'register_type with MessagePack::Timestamp' do | ||
let(:factory) do | ||
factory = MessagePack::Factory.new | ||
factory.register_type(MessagePack::Timestamp::TYPE, MessagePack::Timestamp) | ||
factory | ||
end | ||
|
||
let(:timestamp) { MessagePack::Timestamp.new(Time.now.tv_sec, 123_456_789) } | ||
it 'serializes and deserializes MessagePack::Timestamp' do | ||
packed = factory.pack(timestamp) | ||
unpacked = factory.unpack(packed) | ||
expect(unpacked).to eq(timestamp) | ||
end | ||
end | ||
|
||
describe 'timestamp32' do | ||
it 'handles [1, 0]' do | ||
t = MessagePack::Timestamp.new(1, 0) | ||
|
||
payload = t.to_msgpack_ext | ||
unpacked = MessagePack::Timestamp.from_msgpack_ext(payload) | ||
|
||
expect(unpacked).to eq(t) | ||
end | ||
end | ||
|
||
describe 'timestamp64' do | ||
it 'handles [1, 1]' do | ||
t = MessagePack::Timestamp.new(1, 1) | ||
|
||
payload = t.to_msgpack_ext | ||
unpacked = MessagePack::Timestamp.from_msgpack_ext(payload) | ||
|
||
expect(unpacked).to eq(t) | ||
end | ||
end | ||
|
||
describe 'timestamp96' do | ||
it 'handles [-1, 0]' do | ||
t = MessagePack::Timestamp.new(-1, 0) | ||
|
||
payload = t.to_msgpack_ext | ||
unpacked = MessagePack::Timestamp.from_msgpack_ext(payload) | ||
|
||
expect(unpacked).to eq(t) | ||
end | ||
|
||
it 'handles [-1, 999_999_999]' do | ||
t = MessagePack::Timestamp.new(-1, 999_999_999) | ||
|
||
payload = t.to_msgpack_ext | ||
unpacked = MessagePack::Timestamp.from_msgpack_ext(payload) | ||
|
||
expect(unpacked).to eq(t) | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI the above crazy indentations are made by rubocop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about this code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rubocop fixes it to:
but yes, it's better than the current, so I've applied it f49a8ff
I wonder the rubocop behavior that it sometimes seems to change the code at random.