Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #168 from gfx/timestamp_type
MessagePack timestamp type
- Loading branch information
Showing
8 changed files
with
282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
module MessagePack | ||
|
||
# MessagePack::Time provides packer and unpacker functions for a timestamp type. | ||
# @example Setup for DefaultFactory | ||
# MessagePack::DefaultFactory.register_type( | ||
# MessagePack::Timestamp::TYPE, | ||
# Time, | ||
# packer: MessagePack::Time::Packer, | ||
# unpacker: MessagePack::Time::Unpacker | ||
# ) | ||
class Time | ||
# A packer function that packs a Time instance to a MessagePack timestamp. | ||
Packer = lambda { |payload| | ||
# ... | ||
} | ||
|
||
# An unpacker function that unpacks a MessagePack timestamp to a Time instance. | ||
Unpcker = lambda { |time| | ||
# ... | ||
} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
module MessagePack | ||
# A utility class for MessagePack timestamp type | ||
class Timestamp | ||
# | ||
# 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 | ||
|
||
# @return [Integer] Second part of the timestamp. | ||
attr_reader :sec | ||
|
||
# @return [Integer] Nanosecond part of the timestamp. | ||
attr_reader :nsec | ||
|
||
# @param [Integer] sec | ||
# @param [Integer] nsec | ||
def initialize(sec, nsec) | ||
end | ||
|
||
# @example An unpacker implementation for the Time class | ||
# lambda do |payload| | ||
# tv = MessagePack::Timestamp.from_msgpack_ext(payload) | ||
# Time.at(tv.sec, tv.nsec, :nanosecond) | ||
# end | ||
# | ||
# @param [String] data | ||
# @return [MessagePack::Timestamp] | ||
def self.from_msgpack_ext(data) | ||
end | ||
|
||
# @example A packer implementation for the Time class | ||
# unpacker = lambda do |time| | ||
# MessagePack::Timestamp.to_msgpack_ext(time.tv_sec, time.tv_nsec) | ||
# end | ||
# | ||
# @param [Integer] sec | ||
# @param [Integer] nsec | ||
# @return [String] | ||
def self.to_msgpack_ext(sec, nsec) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# frozen_string_literal: true | ||
|
||
module MessagePack | ||
class Timestamp # a.k.a. "TimeSpec" | ||
# 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 | ||
|
||
# @return [Integer] | ||
attr_reader :sec | ||
|
||
# @return [Integer] | ||
attr_reader :nsec | ||
|
||
# @param [Integer] sec | ||
# @param [Integer] nsec | ||
def initialize(sec, nsec) | ||
@sec = sec | ||
@nsec = nsec | ||
end | ||
|
||
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 MalformedFormatError, "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 | ||
|
||
def ==(other) | ||
other.class == self.class && sec == other.sec && nsec == other.nsec | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'spec_helper' | ||
|
||
describe MessagePack::Timestamp do | ||
describe 'malformed format' do | ||
it do | ||
expect do | ||
MessagePack::Timestamp.from_msgpack_ext([0xd4, 0x00].pack("C*")) | ||
end.to raise_error(MessagePack::MalformedFormatError) | ||
end | ||
end | ||
|
||
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 |