在Ruby中转换日期时间和时间的方法

139

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


1
我不确定这是否应该是一个单独的问题,但是如何在日期和时间之间进行转换? - Andrew Grimm
10
根据现代版本的Ruby,被接受和评价最高的答案已不再是最准确的。请查看下面由@theTinMan和@PatrickMcKenzie提供的答案。 - Phrogz
7个回答

192
require 'time'
require 'date'

t = Time.now
d = DateTime.now

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

13
+1 这可能不是最高效的执行方式,但它有效、简明,且易于阅读。 - Walt Jones
6
不幸的是,这只有在处理本地时间时才真正有效。如果你使用带有不同时区的DateTime或Time开始,解析功能会将其转换为本地时区。你基本上会失去原始时区信息。 - Bernard
6
从 Ruby 1.9.1 开始,DateTime.parse 会保留时区信息。(我无法访问早期版本。)Time.parse 不会保留时区信息,因为它表示 POSIX 标准的 time_t,这是相对于纪元的整数差值。任何转换为 Time 的操作应该具有相同的行为。 - anshul
1
你是正确的。DateTime.parse在1.9.1中可以工作,但Time.parse不能。不管怎样,使用DateTime.new(...)和Time.new(..)更少出错(一致性)且可能更快。有关示例代码,请参见我的答案。 - Bernard
1
嗨@anshul。我不是在意,我是在陈述:-)。当使用Time.parse()时不会保留时区信息。测试很容易。在你上面的代码中,只需用以下内容替换 d = DateTime.now d = DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24))。tt现在将显示将日期d转换为您本地时区的日期。您仍然可以执行日期计算和所有其他操作,但原始的tz信息已丢失。这个信息是日期的上下文,通常非常重要。请参阅:https://dev59.com/h3VC5IYBdhLWcg3wfhGL#3513247 - Bernard
显示剩余5条评论

68
作为 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

1
DateTime.to_time返回一个DateTime...1.9.3p327 :007 > ts = '2000-01-01 12:01:01 -0700' => "2000-01-01 12:01:01 -0700" 1.9.3p327 :009 > dt = ts.to_datetime => Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :010 > dt.to_time => Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :011 > dt.to_time.class => DateTime - Jesse Clark
糟糕。刚刚意识到这是一个Ruby on Rails问题,而不是Ruby问题:https://dev59.com/dWXWa4cB1Zd3GeqPOZi3。甚至还有一个针对2.x线中此方法的错误报告,并将其标记为“不修复”。我的看法是这是可怕的决定。Rails的行为完全破坏了底层的Ruby接口。 - Jesse Clark

53

您需要进行两种略微不同的转换。

要将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

对于日期的类似调整,您可以将 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时间之间进行选择。
上述代码片段均取自O'Reilly的Ruby Cookbook。他们的代码重用policy允许这样做。

5
这段代码在Ruby 1.9版本中会出错,因为DateTime#sec_fraction方法返回一秒钟的毫秒数。在Ruby 1.9中,你需要使用以下代码:usec = dest.sec_fraction * 10**6。该代码将把毫秒转换成微秒。 - dkubb

12

很遗憾,DateTime.to_time, Time.to_datetimeTime.parse函数无法保留时区信息。在转换期间,所有内容都将转换为本地时间。日期算法仍然有效,但您将无法以原始时区显示日期。该上下文信息通常非常重要。例如,如果我想查看在纽约营业时间执行的交易,我可能更喜欢看到它们以其原始时区显示,而不是我的本地时区(比纽约提前12个小时)。

以下是保留该时区信息的转换方法。

对于Ruby 1.8,请参见Gordon Wilson的回答。这是来自可靠的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

完整的原始DateTime信息包括时区已被保留。

2
时间很复杂,但没有不提供不同内置时间类之间的内置转换的借口。如果您尝试获取公元前4713年的UNIX time_t,则可以抛出RangeException(尽管BigNum负值会更好),但至少要提供一种方法。 - Mark Reed
1
Time#to_datetime 看起来对我来说会保留时区:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00" - Phrogz
@Phrogz UTC 偏移量和时区不是同一回事。一个是恒定的,另一个可以因夏令时在不同时间改变。DateTime 没有时区,它忽略 DST。Time 尊重它,但仅限于“本地”(系统环境)TZ。 - Andrew Vit
现在问题已经解决,您可以直接使用.to_timeDateTime.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) - Adit Saxena

1

在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方法


0
在进行这样的转换时,应考虑时区的行为,以便从一个对象转换到另一个对象。我在这个stackoverflow post中找到了一些好的注释和示例。

-1

你可以使用 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接