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)
# => "\x04\bU:\rDateTime[\vi\x00i\x03\xE0\x7F%i\x02s\xC9i\x04\xF8z\xF1\"i\xFE\xB0\xB9f\f2299161"
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, e.g. 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: What is 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 (e.g. 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 e.g. 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"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With