【问题标题】:Parsing JSON datetime?解析 JSON 日期时间?
【发布时间】:2013-06-08 00:33:31
【问题描述】:

我正在使用一个 JSON API,它以以下格式将日期时间值作为字符串返回:

/日期(1370651105153)/

如何将这样的值解析为 Rails 中的日期时间变量?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3


    【解决方案1】:

    这似乎是一个 UNIX 时间戳(自纪元以来的秒数)。此外,它似乎是自纪元以来的毫秒数。

    所以你可以像这样转换它 - 假设 value 是一个看起来像这样的字符串:

    value = "/Date(1370651105153)/"
    
    if value =~ /\/Date\((\d+)\)\//
      timestamp = $1.to_i
      time = Time.at(timestamp / 1000)
      # time is now a Time object
    end
    

    您需要除以 1000,因为 Time#at 期望它的参数是秒数而不是自纪元以来的毫秒数。

    【讨论】:

      【解决方案2】:

      andyisnowskynet 的回答 (https://stackoverflow.com/a/25817039/5633460) 中存在一些错误:RegEx 存在一个小错误,这意味着它不会检测到正时区;并且它不支持timezones that aren't aligned to full hours,例如-0430 (VET)。此外,它返回的Time 对象可能遵循正确的 Ruby 时区处理,而不是添加时间偏移,这将是更传统的方法。

      这是我对该答案的改进版本...

      首先,对于以这种格式表示的日期时间值,时间序列部分(例如 OP 示例中的 1370651105153)始终是自 1970-01-01 00:00:00 GMT 以来的毫秒数,并且不受时区存在与否的影响后缀。因此,作为字符串的一部分包含的任何时区都不会更改它所代表的历史点。它仅用于说明“观察者”在哪个时区当时

      Ruby Time 对象能够处理这两条信息(即实际的日期时间“值”和时区“元数据”)。演示:

      a = Time.at(-1).utc
      # => 1969-12-31 23:59:59 UTC
      b = Time.at(-1).getlocal('+09:30')
      # => 1970-01-01 09:29:59 +0930
      a == b
      # => true
      

      如您所见,即使两者看起来像不同的值(由于它们表达它们的时区不同),== 相等运算符表明它们实际上引用了相同的时刻 .

      知道了这一点,我们可以对parse_json_datetime 进行如下改进(也纠正其他错误):

      def parse_json_datetime(datetime)
        # "/Date(-62135575200000-0600)/" seems to be the default date returned
        # if the value is null:
        if datetime == "/Date(-62135575200000-0600)/"
          # Return nil because it is probably a null date that is defaulting to 0.
          # To be more technically correct you could just return 0 here if you wanted:
          return nil
        elsif datetime =~ %r{/Date\(([-+]?\d+)([-+]\d+)?\)/}
          # We've now parsed the string into:
          # - $1: Number of milliseconds since the 1/1/1970 epoch.
          # - $2: [Optional] timezone offset.
          # Divide $1 by 1000 because it is in milliseconds and Time uses seconds:
          seconds_since_epoch = $1.to_i / 1000.0
          time = Time.at(seconds_since_epoch.to_i).utc 
          # We now have the exact moment in history that this represents,
          # stored as a UTC-based "Time" object.
      
          if $2
            # We have a timezone, so convert its format (adding a colon)...
            timezone = $2.gsub(/(...)(..)/, '\1:\2')
            # ...then apply it to the Time object:
            time = time.getlocal(timezone)
          end
          time
        else
          raise "Unrecognized date format."
        end
      end
      

      然后我们可以测试:

      # See: http://momentjs.com/docs/#/parsing/asp-net-json-date/
      parse_json_datetime("/Date(1198908717056-0700)/")
      # => 2007-12-28 23:11:57 -0700
      
      # 1 minute before the epoch, but in ACST:
      parse_json_datetime("/Date(-60000+0930)/")
      # => 1970-01-01 09:29:00 +0930
      
      # Same as above, but converted naturally to its UTC equivalent:
      parse_json_datetime("/Date(-60000+0930)/").utc
      # => 1969-12-31 23:59:00 UTC
      
      # Same as above, with timezone unspecified (implying UTC):
      parse_json_datetime("/Date(-60000)/")
      # => 1969-12-31 23:59:00 UTC
      
      # OP's example:
      parse_json_datetime("/Date(1370651105153)/")
      # => 2013-06-08 00:25:05 UTC
      
      # Same as above, but stated in two different timezones:
      aaa = parse_json_datetime("/Date(1370651105153-0200)/")
      # => 2013-06-07 22:25:05 -0200
      bbb = parse_json_datetime("/Date(1370651105153-0800)/")
      # => 2013-06-07 16:25:05 -0800
      
      # As "rendered" strings they're not the same:
      aaa.to_s == bbb.to_s
      # => false
      # But as moments in time, they are equivalent:
      aaa == bbb
      # => true
      # And, for the sake of the argument, if they're both expressed in the
      # same timezone (arbitrary '-04:00' in this case) then they also render
      # as equal strings:
      aaa.getlocal('-04:00').to_s == bbb.getlocal('-04:00').to_s
      

      【讨论】:

        【解决方案3】:

        整数似乎是一个 unixtime(以毫秒为单位)。只需剪掉最后三位数字并将其余数字提供给Time.at

        Time.at(1370651105) # => 2013-06-08 04:25:05 +0400
        

        【讨论】:

          【解决方案4】:

          对于需要包含时区的更强大解决方案的任何人,这是我想出的解析器:

          def parse_json_datetime(datetime)
            # "/Date(-62135575200000-0600)/" seems to be the default date returned if the value is null
            if datetime == "/Date(-62135575200000-0600)/"
              # return nil because it is probably a null date that is defaulting to 0.
              # to be more technically correct you could just return 0 here if you wanted.
              return nil
            elsif datetime =~ /\/Date\(([-+]?\d+)(-+\d+)?\)\// # parse the seconds and timezone (if present)
              milliseconds_since_epoch = $1
              time = Time.at(milliseconds_since_epoch.to_i/1000.0).utc # divide by 1000 because it is in milliseconds and Time uses seconds
          
              if timezone_hourly_offset = $2
                timezone_hourly_offset = timezone_hourly_offset.gsub("0","").to_i
                time = time+(timezone_hourly_offset*60*60) # convert hourly timezone offset into seconds and add onto time
              end
              time
            else
              raise "Unrecognized date format."
            end
          end
          

          【讨论】:

            【解决方案5】:

            Active Support makes it easy to do this:

            #!/usr/bin/env ruby
            
            require 'active_support/json'
            require 'active_support/time'
            
            Time.zone = 'Eastern Time (US & Canada)'
            ActiveSupport.parse_json_times = true
            
            puts ActiveSupport::JSON.decode('{"hi":"2009-08-10T19:01:02Z"}')
            

            将输出:

            {"hi"=>Mon, 10 Aug 2009 15:01:02 EDT -04:00}
            

            一旦您打开ActiveSupport.parse_json_times 标志,那么您解码的任何JSON 有效负载都会自动转换为ActiveSupport::TimeWithZone 对象。

            积极支持uses a regex to detect times。您可以read the tests 了解它支持的时间格式。看起来它支持 ISO 8601,但支持 Unix/POSIX/Epoch 时间戳。

            当我创建 this issue against Rails 时,我在 Active Support 中得知了此功能。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-01-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多