【发布时间】:2013-06-08 00:33:31
【问题描述】:
我正在使用一个 JSON API,它以以下格式将日期时间值作为字符串返回:
/日期(1370651105153)/
如何将这样的值解析为 Rails 中的日期时间变量?
【问题讨论】:
标签: ruby-on-rails ruby-on-rails-3
我正在使用一个 JSON API,它以以下格式将日期时间值作为字符串返回:
/日期(1370651105153)/
如何将这样的值解析为 Rails 中的日期时间变量?
【问题讨论】:
标签: ruby-on-rails ruby-on-rails-3
这似乎是一个 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 期望它的参数是秒数而不是自纪元以来的毫秒数。
【讨论】:
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
【讨论】:
整数似乎是一个 unixtime(以毫秒为单位)。只需剪掉最后三位数字并将其余数字提供给Time.at:
Time.at(1370651105) # => 2013-06-08 04:25:05 +0400
【讨论】:
对于需要包含时区的更强大解决方案的任何人,这是我想出的解析器:
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
【讨论】:
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 中得知了此功能。
【讨论】: