在 Ruby 中转换为日期时间和时间

Posted

技术标签:

【中文标题】在 Ruby 中转换为日期时间和时间【英文标题】:Convert to/from DateTime and Time in Ruby 【发布时间】:2010-09-21 17:47:10 【问题描述】:

如何在 Ruby 中的 DateTime 和 Time 对象之间进行转换?

【问题讨论】:

我不确定这是否应该是一个单独的问题,但是您如何在日期和时间之间进行转换? 接受和评价最高的答案在现代版本的 Ruby 下不再是最准确的。请参阅下面的答案 by @theTinMan 和 by @PatrickMcKenzie。 【参考方案1】:

您需要两次略有不同的转换。

要从 Time 转换为 DateTime,您可以修改Time 类,如下所示:

require 'date'
class Time
  def to_datetime
    # Convert seconds + microseconds into a fractional number of seconds
    seconds = sec + Rational(usec, 10**6)

    # Convert a UTC offset measured in minutes to one measured in a
    # fraction of a day.
    offset = Rational(utc_offset, 60 * 60 * 24)
    DateTime.new(year, month, day, hour, min, seconds, offset)
  end
end

对 Date 的类似调整可以让您将 DateTime 转换为 Time

class Date
  def to_gm_time
    to_time(new_offset, :gm)
  end

  def to_local_time
    to_time(new_offset(DateTime.now.offset-offset), :local)
  end

  private
  def to_time(dest, method)
    #Convert a fraction of a day to a number of microseconds
    usec = (dest.sec_fraction * 60 * 60 * 24 * (10**6)).to_i
    Time.send(method, dest.year, dest.month, dest.day, dest.hour, dest.min,
              dest.sec, usec)
  end
end

请注意,您必须在本地时间和 GM/UTC 时间之间进行选择。

以上代码 sn-ps 均取自 O'Reilly 的Ruby Cookbook。他们的代码重用policy 允许这样做。

【讨论】:

这将在 1.9 中断,其中 DateTime#sec_fraction 返回一秒内的毫秒数。对于您要使用的 1.9:usec = dest.sec_fraction * 10**6【参考方案2】:
require 'time'
require 'date'

t = Time.now
d = DateTime.now

dd = DateTime.parse(t.to_s)
tt = Time.parse(d.to_s)

【讨论】:

+1 这可能不是执行效率最高的,但它有效,简洁,可读性强。 不幸的是,这只在处理当地时间时才真正有效。如果您从具有不同时区的 DateTime 或 Time 开始,则解析函数将转换为本地时区。你基本上失去了原来的时区。 从 ruby​​ 1.9.1 开始,DateTime.parse 确实保留了时区。 (我无权访问早期版本。) Time.parse 不保留时区,因为它代表 POSIX 标准 time_t,我相信它与纪元的整数差异。任何到 Time 的转换都应该具有相同的行为。 你是对的。 DateTime.parse 适用于 1.9.1 但不适用于 Time.parse。无论如何,使用 DateTime.new(...) 和 Time.new(..) 更不容易出错(一致)并且可能更快。有关示例代码,请参阅我的答案。 嗨@anshul。我并不是在暗示我在说:-)。使用 Time.parse() 时不保留时区信息。这很容易测试。在上面的代码中,只需将 d = DateTime.now 替换为 d = DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24))。 tt 现在将显示转换为您当地时区的日期。您仍然可以进行日期算术运算,并且除了原始 tz 信息之外的所有信息都会丢失。此信息是日期的上下文,通常很重要。见这里:***.com/questions/279769/…【参考方案3】:

很遗憾,DateTime.to_time, Time.to_datetimeTime.parse 函数不保留时区信息。转换期间所有内容都转换为本地时区。日期算术仍然有效,但您将无法显示带有原始时区的日期。上下文信息通常很重要。例如,如果我想查看在纽约工作时间内执行的交易,我可能更愿意看到它们显示在原来的时区,而不是我在澳大利亚的本地时区(比纽约早 12 小时)。

下面的转换方法确实保留了该 tz 信息。

对于 Ruby 1.8,请查看 Gordon Wilson's answer。它来自可靠的 Ruby Cookbook。

对于 Ruby 1.9,它稍微容易一些。

require 'date'

# Create a date in some foreign time zone (middle of the Atlantic)
d = DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24))
puts d

# Convert DateTime to Time, keeping the original timezone
t = Time.new(d.year, d.month, d.day, d.hour, d.min, d.sec, d.zone)
puts t

# Convert Time to DateTime, keeping the original timezone
d = DateTime.new(t.year, t.month, t.day, t.hour, t.min, t.sec, Rational(t.gmt_offset / 3600, 24))
puts d

这会打印以下内容

2010-01-01T10:00:00-02:00
2010-01-01 10:00:00 -0200
2010-01-01T10:00:00-02:00

保留完整的原始日期时间信息,包括时区。

【讨论】:

时间很复杂,但没有任何借口不提供不同内置时间类之间的内置转换。如果您尝试获取 4713 BC 的 UNIX time_t,则可以抛出 RangeException(尽管 BigNum 负值会更好),但至少提供一个方法。 Time#to_datetime 似乎为我保留了 tz:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00" @Phrogz UTC 偏移量与时区不同。一个是恒定的,另一个可以在一年中的不同时间更改以用于夏令时。 DateTime 没有区域,它忽略 DST。时间尊重它,但仅限于“本地”(系统环境)TZ。 现在已经解决了,你可以做.to_time:DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24)).to_time == Time.new(d.year, d.month, d.day, d.hour, d.min, d.sec, d.zone)(2010-01-01T10:00:00-02:00 / 2010-01-01 10:00:00 -0200)【参考方案4】:

作为 Ruby 生态系统状态的更新,DateDateTimeTime 现在具有在各种类之间转换的方法。使用 Ruby 1.9.2+:

pry
[1] pry(main)> ts = 'Jan 1, 2000 12:01:01'
=> "Jan 1, 2000 12:01:01"
[2] pry(main)> require 'time'
=> true
[3] pry(main)> require 'date'
=> true
[4] pry(main)> ds = Date.parse(ts)
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[5] pry(main)> ds.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[6] pry(main)> ds.to_datetime
=> #<DateTime: 2000-01-01T00:00:00+00:00 (4903089/2,0,2299161)>
[7] pry(main)> ds.to_time
=> 2000-01-01 00:00:00 -0700
[8] pry(main)> ds.to_time.class
=> Time
[9] pry(main)> ds.to_datetime.class
=> DateTime
[10] pry(main)> ts = Time.parse(ts)
=> 2000-01-01 12:01:01 -0700
[11] pry(main)> ts.class
=> Time
[12] pry(main)> ts.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[13] pry(main)> ts.to_date.class
=> Date
[14] pry(main)> ts.to_datetime
=> #<DateTime: 2000-01-01T12:01:01-07:00 (211813513261/86400,-7/24,2299161)>
[15] pry(main)> ts.to_datetime.class
=> DateTime

【讨论】:

DateTime.to_time 返回一个 DateTime...1.9.3p327 :007 &gt; ts = '2000-01-01 12:01:01 -0700' =&gt; "2000-01-01 12:01:01 -0700" 1.9.3p327 :009 &gt; dt = ts.to_datetime =&gt; Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :010 &gt; dt.to_time =&gt; Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :011 &gt; dt.to_time.class =&gt; DateTime 糟糕。刚刚意识到这是一个 Ruby on Rails 问题而不是 Ruby 问题:***.com/questions/11277454/…。他们甚至在 2.x 行中针对此方法提交了一个错误,并将其标记为“无法修复”。恕我直言,可怕的决定。 Rails 的行为完全破坏了底层的 Ruby 接口。【参考方案5】:

改进 Gordon Wilson 解决方案,这是我的尝试:

def to_time
  #Convert a fraction of a day to a number of microseconds
  usec = (sec_fraction * 60 * 60 * 24 * (10**6)).to_i
  t = Time.gm(year, month, day, hour, min, sec, usec)
  t - offset.abs.div(SECONDS_IN_DAY)
end

您将在 UTC 中获得相同的时间,失去时区(不幸的是)

另外,如果你有 ruby​​ 1.9,试试to_time 方法

【讨论】:

【参考方案6】:

在进行此类转换时,应考虑从一个对象转换到另一个对象时的时区行为。我在这个 *** post 中找到了一些很好的注释和示例。

【讨论】:

【参考方案7】:

你可以使用to_date,例如

> Event.last.starts_at
=> Wed, 13 Jan 2021 16:49:36.292979000 CET +01:00
> Event.last.starts_at.to_date
=> Wed, 13 Jan 2021

【讨论】:

以上是关于在 Ruby 中转换为日期时间和时间的主要内容,如果未能解决你的问题,请参考以下文章

将标准 rails / ruby​​ 时间转换为美国格式 - 日期混淆

Ruby:将unix时间戳转换为日期[重复]

将 Ruby 日期转换为整数

Ruby/Rails:将日期时间转换为自然语言(例如 2012 年 3 月 23 日到“本周五”)

将普通日期转换为 unix 时间戳

从日期集合中查找平均日期(Ruby)