DateTime serialization and deserialization

I'd like to serialize a Ruby DateTime object to json. Unfortunately, my approach is not symetrical:

require 'date'
date = DateTime.now
DateTime.parse(date.to_s) == date
 => false

I could use some arbitrary strftime/parse string combination, but I believe there must be a better approach.


The accepted answer is not a good solution, unfortunately. As always, marshal/unmarshal is a tool you should only use as a last resort, but in this case it will probably break your app.

OP specifically mentioned serializing a date to JSON. Per RFC 7159:

JSON text SHALL be encoded in UTF-8, UTF-16, or UTF-32. The default encoding is UTF-8, and JSON texts that are encoded in UTF-8 are interoperable in the sense that they will be read successfully by the maximum number of implementations; there are many implementations that cannot successfully read texts in other encodings (such as UTF-16 and UTF-32).

Now let's look at what we get from Marshal:

marsh = Marshal.dump(DateTime.now)
# => "x04bU:rDateTime[vix00ix03xE0x7F%ix02sxC9ix04xF8zxF1"ixFExB0xB9ff2299161"
puts marsh.encoding
# -> #<Encoding:ASCII-8BIT>

marsh.encode(Encoding::UTF_8)
# -> Encoding::UndefinedConversionError: "xE0" from ASCII-8BIT to UTF-8

In addition to returning a value that isn't human-readable, Marshal.dump gives us a value that can't be converted to UTF-8. That means the only way to put it into (valid) JSON is to encode it somehow, eg base-64.

There's no need to do that. There's already a very interoperable way to represent dates and times: ISO 8601. I won't go over why it's the best choice for JSON (and in general), but the answers here cover it well: The "right" JSON date format.

Since Ruby 1.9.3 the DateTime class has had iso8601 class and instance methods to parse and format ISO 8601 dates, respectively. The latter takes an argument to specify precision for fractional seconds (eg 3 for milliseconds):

require "date"

date = DateTime.now
str = date.iso8601(9)
puts str
# -> 2016-06-28T09:35:58.311527000-05:00

DateTime.iso8601(str) == date
# => true

Note that if you specify a smaller precision, this might not work, because eg 58.311 is not equal to 58.311527 . A precision of 9 (nanosecond) seems safe to me, since the DateTime docs say:

The fractional number's precision is assumed at most nanosecond.

However, if you're interoperating with systems that might use greater precision, you should take that into consideration.

Finally, if you want to make Ruby's JSON library automatically use iso8601 for serialization, override the as_json and to_json methods:

unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
  require 'json'
end
require 'date'

class DateTime
  def as_json(*)
    iso8601(9)
  end

  def to_json(*args)
    as_json.to_json(*args)
  end
end

puts DateTime.now.to_json
# -> "2016-06-28T09:35:58.311527000-05:00"

Both the to_s method and the to_json method (provided require 'json' ) ignore the nanoseconds which are stored by the DateTime object date . Good old Marshal delivers:

require 'date'
date = DateTime.now
m_date = Marshal.dump(date)
p Marshal.load(m_date) == date # => true

It is because date has sub second value, and #to_s method will return ISO time format in seconds, the comparison don't succeed.

1.9.3p327 :021 > date = DateTime.now
 => #<DateTime: 2012-11-28T07:32:40+09:00 ((2456259j,81160s,283019000n),+32400s,2299161j)> 
1.9.3p327 :022 > DateTime.parse(date.to_s)
 => #<DateTime: 2012-11-28T07:32:40+09:00 ((2456259j,81160s,0n),+32400s,2299161j)> 

so they're actually different.

If you don't care about sub-seconds, just forget whether comparison succeed or not.

Or, you can use DateTime#marshal_load and DateTime#marshal_dump for 1.9.3. (I didn't know this till now.. )

It work as:

date1 = DateTime.now
dump  = date1.marshal_dump
date2 = DateTime.new.marshal_load(dump)
date1 == date2 # => true
链接地址: http://www.djcxy.com/p/46670.html

上一篇: 使用SwiftyJSON来反序列化NSDate

下一篇: DateTime序列化和反序列化