4

我想将 Ruby DateTime 对象序列化为 json。不幸的是,我的方法不是对称的:

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

我可以使用一些任意的 strftime/parse 字符串组合,但我相信必须有更好的方法。

4

3 回答 3

9

不幸的是,接受的答案不是一个好的解决方案。与往常一样,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(以及一般情况下)的最佳选择,但这里的答案很好地涵盖了它:什么是“正确的”JSON 日期格式?.

从 Ruby 1.9.3 开始,DateTime 类具有分别用于解析和格式化 ISO 8601 日期的iso8601 实例方法。后者需要一个参数来指定小数秒的精度(例如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

请注意,如果您指定较小的精度,这可能不起作用,因为 eg58.311不等于58.311527. (纳秒)的精度9对我来说似乎是安全的,因为 DateTime 文档说:

假设小数的精度最多为纳秒。

但是,如果您正在与可能使用更高精度的系统进行互操作,则应该考虑到这一点。

最后,如果您想让 Ruby 的 JSON 库自动iso8601用于序列化,请覆盖as_jsonto_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"
于 2016-06-28T15:01:31.480 回答
3

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
于 2012-11-27T22:51:27.097 回答
2

这是因为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)> 

所以它们实际上是不同的。

如果你不关心亚秒,就忘记比较是否成功。

或者,您可以将DateTime#marshal_loadandDateTime#marshal_dump用于 1.9.3。(直到现在我才知道这个..)

它的工作原理是:

date1 = DateTime.now
dump  = date1.marshal_dump
date2 = DateTime.new.marshal_load(dump)
date1 == date2 # => true
于 2012-11-27T22:35:29.403 回答