【问题标题】:DateTime serialization and deserializationDateTime 序列化和反序列化
【发布时间】:2012-11-27 22:26:23
【问题描述】:

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

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

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

【问题讨论】:

    标签: ruby datetime serialization


    【解决方案1】:

    不幸的是,接受的答案不是一个好的解决方案。与往常一样,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 classinstance 方法来分别解析和格式化 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.3115279(纳秒)的精度对我来说似乎是安全的,因为 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"
    

    【讨论】:

    • 感谢您的详细分析,我不知道utf-8的问题
    【解决方案2】:

    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
    

    【讨论】:

      【解决方案3】:

      这是因为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_loadDateTime#marshal_dump。 (直到现在我才知道..)

      它的作用是:

      date1 = DateTime.now
      dump  = date1.marshal_dump
      date2 = DateTime.new.marshal_load(dump)
      date1 == date2 # => true
      

      【讨论】:

        猜你喜欢
        • 2015-10-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-08
        • 2020-05-25
        相关资源
        最近更新 更多