Skip to content
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

Shall not include Time conversion by default #58

Open
vladfaust opened this issue Apr 24, 2019 · 11 comments
Open

Shall not include Time conversion by default #58

vladfaust opened this issue Apr 24, 2019 · 11 comments

Comments

@vladfaust
Copy link
Contributor

It's even stated in the docs:

# The MsgPack format itself does not specify a time data type, this method just
# assumes that a string holding a ISO 8601 time format can be # interpreted as a
# time value.

I suggest that proper implementation does not "assume" anything. There should be a converter for anything which is out of MessagePack specification scope.

A Time can be transferred either as ISO 8601 string or epoch seconds or even epoch milliseconds. There should not be a default constructor for Time IMO.

@kostya
Copy link
Collaborator

kostya commented Apr 30, 2019

doc not updated, why not, i think its ok, if you want special format you set it.

@vladfaust
Copy link
Contributor Author

It's bad because your're defining the single possible variant for Time parsing from msgpack, and this should not be a case. It's better to have something like Time::StringConverter or so.

@kostya
Copy link
Collaborator

kostya commented May 1, 2019

this not single possible variant, you can do many things:

require "./src/msgpack"

msg = Time.now.to_msgpack(Time::Format.new("%F"))
p msg
p String.from_msgpack(msg)
time = Time.from_msgpack(Time::Format.new("%F"), msg)
p time

class A
  include MessagePack::Serializable

  @[MessagePack::Field(converter: Time::Format.new("%F %T"))]
  property value : Time?
end

a = A.new
a.value = Time.now

msg = a.to_msgpack
p msg
p A.from_msgpack(msg)
Bytes[170, 50, 48, 49, 57, 45, 48, 53, 45, 48, 49]
"2019-05-01"
2019-05-01 00:00:00.0 UTC
Bytes[129, 165, 118, 97, 108, 117, 101, 179, 50, 48, 49, 57, 45, 48, 53, 45, 48, 49, 32, 49, 51, 58, 52, 57, 58, 52, 55]
#<A:0x1006cdea0 @value=2019-05-01 13:49:47.0 UTC>

maybe string conversion by default not optimal, but default case just simplify things.

@vladfaust
Copy link
Contributor Author

What if me or a third party wants to store time in Unix seconds?

@kostya
Copy link
Collaborator

kostya commented May 1, 2019

struct UnixConverter
  def self.from_msgpack(unpacker)
    Time.unix_ms(unpacker.read_int)
  end

  def self.to_msgpack(value, packer)
    packer.write(value.to_unix_ms)
  end
end

msg = Time.now.to_unix_ms.to_msgpack
p msg
p Int64.from_msgpack(msg)
p UnixConverter.from_msgpack(MessagePack::IOUnpacker.new(msg))

class B
  include MessagePack::Serializable

  @[MessagePack::Field(converter: UnixConverter)]
  property value : Time?
end

a = B.new
a.value = Time.now

msg = a.to_msgpack
p msg
p B.from_msgpack(msg)
Bytes[207, 0, 0, 1, 106, 115, 26, 101, 110]
1556709270894
2019-05-01 11:14:30.894000000 UTC
Bytes[129, 165, 118, 97, 108, 117, 101, 207, 0, 0, 1, 106, 115, 26, 101, 111]
#<B:0x10d631ea0 @value=2019-05-01 11:14:30.895000000 UTC>

@vladfaust
Copy link
Contributor Author

Yes, so why do we use a converter for unix time but don't use one for ISO formatted time? Why does ISO format has higher "precedence" in this implementation? That's what I'm talking about.

@kostya
Copy link
Collaborator

kostya commented May 1, 2019

may be unix by default would be better, i just choose the same as json converter.

@vladfaust
Copy link
Contributor Author

There should not be any defaults IMO unless stated otherwise by MessagePack specs. It is a breaking change indeed, but it would reduce possible confusion. I guess I have to also create an issue in Crystal itself, but afraid that the core team would deny it due to "it works as it works" and "it's too breaking" and "you're bikeshedding". We'll see.

Regarding to this repo, I could create a PR creating TimeFormat and also Epoch (Unix?) converters.

@kostya
Copy link
Collaborator

kostya commented May 5, 2019

i not sure that default need to be deleted, at least you can redefine default also:

require "msgpack"

struct Time
  def to_msgpack(packer : MessagePack::Packer)
    packer.write(self.to_unix)
  end

  def self.new(pull : MessagePack::Unpacker)
    Time.unix(pull.read_int)
  end
end

msg = Time.now.to_msgpack
p msg
p Time.from_msgpack(msg)

@didactic-drunk
Copy link
Contributor

I have to agree with no default or a standard extension default. The current default wasted a bunch of time debugging an issue with sub seconds. The current default doesn't store them. Crystal to_s doesn't show them. What seems like two identical Time's compare as false most of the time. I think that should be a bug.

Example:

t1 = Time.now
t2 = Time.from_msgpack t1.to_msgpack

# Because of nanoseconds comparisons can be true or false for the same encoded time value.
t1 == t2 => false # Most of the time.
t1 == t2 => true # 1 in a million.

# Visually looks identical and like == is broken
p t1, t2

@didactic-drunk
Copy link
Contributor

A Time can be transferred either as ISO 8601 string or epoch seconds or even epoch milliseconds. There should not be a default constructor for Time IMO.

I've also seen Floats used. And "#{epoch}.#{milliseconds}" And non ISO time String's. I wouldn't be surprised if someone used TAI in a number format that's easily confused with various epoch encodings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants