我想将一个 Ruby DateTime 对象序列化为 json。不幸的是,我的方法不对称:
require 'date'
date = DateTime.now
DateTime.parse(date.to_s) == date
=> false
我可以使用任意的strftime/parse字符串组合,但我相信一定有更好的方法。
我想将一个 Ruby DateTime 对象序列化为 json。不幸的是,我的方法不对称:
require 'date'
date = DateTime.now
DateTime.parse(date.to_s) == date
=> false
我可以使用任意的strftime/parse字符串组合,但我相信一定有更好的方法。
不幸的是,被接受的答案并不是一个好的解决方案。正如往常一样,marshal / unmarshal 是你应该只在最后使用的工具,但在这种情况下,它可能会破坏你的应用程序。
OP特别提到了将日期序列化为JSON。根据RFC 7159:
JSON文本应该用UTF-8、UTF-16或UTF-32进行编码。默认编码是UTF-8,使用UTF-8编码的JSON文本在互操作性方面是可互换的,最多的实现可以成功地读取这些文本;有许多实现无法成功地读取其他编码(例如UTF-16和UTF-32)的文本。
现在让我们看看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
Marshal.dump
返回的值不易读且不能转换成UTF-8格式,因此将其放入(有效)JSON中的唯一方法是进行编码,例如base-64。
实际上已经有了一种非常互操作的表示日期和时间的方式:ISO 8601。我不会详细说明为什么它是JSON(以及通常)的最佳选择,但这里的答案涵盖得很好:What is the "right" JSON date format?。
自Ruby 1.9.3以来,DateTime类已经具有iso8601
class和instance方法来解析和格式化ISO 8601日期,后者接受一个参数来指定分数秒的精度(例如毫秒:3
):
require "date"
date = DateTime.now
str = date.iso8601(9)
puts str
# -> 2016-06-28T09:35:58.311527000-05:00
DateTime.iso8601(str) == date
# => true
请注意,如果您指定较小的精度,可能会出现问题,因为例如58.311
不等于58.311527
。对于我来说,9
(纳秒)的精度似乎是安全的,因为DateTime文档中说:分数的精度最多假定为纳秒。
但是,如果您正在与可能使用更高精度的系统进行互操作,则应考虑这一点。
最后,如果您想使Ruby的JSON库自动使用iso8601
进行序列化,请覆盖as_json
和to_json
方法:
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"
to_s
方法和to_json
方法(需要require 'json'
)都会忽略DateTime对象date
存储的纳秒。而老牌的Marshal
则会保留:
require 'date'
date = DateTime.now
m_date = Marshal.dump(date)
p Marshal.load(m_date) == date # => true
date
具有亚秒值,而#to_s
方法将以秒为单位返回ISO时间格式,因此比较不成功。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)>
所以它们实际上是不同的。
如果您不关心亚秒,只需忽略比较是否成功即可。
或者,您可以在1.9.3中使用DateTime#marshal_load
和DateTime#marshal_dump
。
(直到现在我才知道这个..)
它的工作方式如下:
date1 = DateTime.now
dump = date1.marshal_dump
date2 = DateTime.new.marshal_load(dump)
date1 == date2 # => true