Ruby中日期时间和时间的区别

Posted

技术标签:

【中文标题】Ruby中日期时间和时间的区别【英文标题】:Difference between DateTime and Time in Ruby 【发布时间】:2010-11-18 16:19:46 【问题描述】:

Ruby 中的DateTimeTime 类有什么区别,哪些因素会导致我选择其中一个?

【问题讨论】:

文档中有a section,解释了何时使用哪个。 【参考方案1】:

较新版本的 Ruby (2.0+) 在这两个类之间并没有真正的显着差异。由于历史原因,一些库会使用其中一种,但不一定需要关注新代码。选择一个以保持一致性可能是最好的,因此请尝试与您的库所期望的相结合。例如,ActiveRecord 更喜欢 DateTime。

在 Ruby 1.9 之前的版本和许多系统上,时间表示为 32 位有符号值,描述自 1970 年 1 月 1 日 UTC 以来的秒数,是 POSIX 标准 time_t 值的精简包装,并且是有界:

Time.at(0x7FFFFFFF)
# => Mon Jan 18 22:14:07 -0500 2038
Time.at(-0x7FFFFFFF)
# => Fri Dec 13 15:45:53 -0500 1901

较新版本的 Ruby 能够处理更大的值而不会产生错误。

DateTime 是一种基于日历的方法,其中单独存储年、月、日、小时、分钟和秒。这是一个 Ruby on Rails 构造,用作 SQL 标准 DATETIME 字段的包装器。它们包含任意日期,几乎可以表示任何时间点,因为表达式的范围通常非常大。

DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000

所以令人放心的是,DateTime 可以处理来自亚里士多德的博客文章。

现在选择一个时,差异有些主观。从历史上看,DateTime 为以日历方式操作它提供了更好的选择,但其中许多方法也已移植到 Time 中,至少在 Rails 环境中是这样。

【讨论】:

那么我应该一直使用 DateTime 吗? 如果您使用日期,我会说使用 DateTime。时间便于表示诸如当前时间之类的事物,或近期的点(例如 10.minutes.from_now)。两者有很多共同点,尽管如前所述,DateTime 可以代表更广泛的值。 我相信这是因为 Ruby 在遇到溢出时会从 32 位 Fixnum 切换到任意长度的 Bignum。外部应用程序可能不支持超出该范围的数字。是的,在 2038 年,我们基本上都被搞砸了,直到我们都能就正确的 64 位时间格式达成一致。陪审团仍未出局。 这个答案早于 1.9.2。忽略它所说的关于 Time 的 posix 限制的所有内容,并根据 Time 和 DateTime 的 API 做出选择。 这个答案已经过时了吧?现在推荐使用 Time 或 Rail 的 ActiveSupport::WithTimeZone。您不再需要使用 DateTime。它是为了向后兼容。【参考方案2】:

[2018 年 7 月编辑]

以下所有内容在 Ruby 2.5.1 中仍然适用。来自reference documentation:

DateTime 不考虑任何闰秒,不跟踪任何夏令时规则。

之前在这个帖子中没有提到的是DateTime 的少数优势之一:它知道日历改革,而Time 不知道:

[...] Ruby 的 Time 类实现了一个公历,没有日历改革的概念 [...]。

参考文档最后建议在专门处理近期、当前或未来日期/时间时使用Time,并且仅在需要准确转换莎士比亚的生日时使用DateTime:(强调添加)

那么在 Ruby 中什么时候应该使用 DateTime,什么时候应该使用 Time?几乎可以肯定,您会想要使用 Time,因为您的应用可能正在处理当前日期和时间。但是,如果您需要在历史背景下处理日期和时间,您将需要使用 DateTime […]。如果您还必须处理时区,那么祝您好运 - 请记住,您可能会处理当地太阳时,因为直到 19 世纪铁路的引入才需要标准时间以及最终的时区。

[/2018 年 7 月编辑]

从 ruby​​ 2.0 开始,其他答案中的大部分信息都已过时。

特别是,Time 现在实际上是未绑定的。距离 Epoch 甚至可以多或少 63 位:

irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> Time.at(2**62-1).utc # within Integer range
=> 146138514283-06-19 07:44:38 UTC
irb(main):003:0> Time.at(2**128).utc # outside of Integer range
=> 10783118943836478994022445751222-08-06 08:03:51 UTC
irb(main):004:0> Time.at(-2**128).utc # outside of Integer range
=> -10783118943836478994022445747283-05-28 15:55:44 UTC

使用较大值的唯一结果应该是性能,当使用Integers(与Bignums(超出Integer范围的值)或Rationals(跟踪纳秒时)相比,性能会更好)):

从 Ruby 1.9.2 开始,Time 实现使用有符号的 63 位整数、Bignum 或 Rational。该整数是自 Epoch 以来的纳秒数,可以表示 1823-11-12 到 2116-02-20。当使用 Bignum 或 Rational 时(在 1823 之前,在 2116 之后,纳秒以下),Time 的工作速度与使用整数时一样慢。 (http://www.ruby-doc.org/core-2.1.0/Time.html)

换句话说,据我所知,DateTime 不再涵盖比Time 更广泛的潜在价值

另外,DateTime 之前没有提到的两个限制可能应该注意:

DateTime 不考虑任何闰秒,不跟踪任何夏令时规则。 (http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime)

首先,DateTime 没有闰秒的概念:

irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require "date"
=> true
irb(main):003:0> t = Time.new(2012,6,30,23,59,60,0)
=> 2012-06-30 23:59:60 +0000
irb(main):004:0> dt = t.to_datetime; dt.to_s
=> "2012-06-30T23:59:59+00:00"
irb(main):005:0> t == dt.to_time
=> false
irb(main):006:0> t.to_i
=> 1341100824
irb(main):007:0> dt.to_time.to_i
=> 1341100823

为了让上面的示例使用Time,操作系统需要支持闰秒并且需要正确设置时区信息,例如通过TZ=right/UTC irb(在许多 Unix 系统上)。

其次,DateTime 对时区的了解非常有限,尤其是没有夏令时的概念。它几乎可以将时区处理为简单的 UTC + X 偏移量:

irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require "date"
=> true
irb(main):003:0> t = Time.local(2012,7,1)
=> 2012-07-01 00:00:00 +0200
irb(main):004:0> t.zone
=> "CEST"
irb(main):005:0> t.dst?
=> true
irb(main):006:0> dt = t.to_datetime; dt.to_s
=> "2012-07-01T00:00:00+02:00"
irb(main):007:0> dt.zone
=> "+02:00"
irb(main):008:0> dt.dst?
NoMethodError: undefined method `dst?' for #<DateTime:0x007f34ea6c3cb8>

当时间输入为 DST 然后转换为非 DST 时区而不跟踪 DateTime 本身之外的正确偏移量时,这可能会导致麻烦(许多操作系统实际上可能已经为您处理了这个问题) .

总的来说,我会说现在Time 是大多数应用程序的更好选择。

还要注意加法的一个重要区别:将数字添加到 Time 对象时,以秒为单位,但将数字添加到 DateTime 时,以天为单位。

【讨论】:

2018年还是这样吗? 在 Ruby 2.5.1 中仍然如此。 ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html:“DateTime 不考虑任何闰秒,不跟踪任何夏令时规则。”但是,请注意,当您需要处理公历之前的日期/时间时,DateTime 具有优势。这一点之前没有在这里提到,但在参考文档中有记录:“[...] Ruby 的 Time 类实现了一个预测的公历,并且没有日历改革的概念 [...]。”我将编辑我的答案以提供更多详细信息。 Time 也没有闰秒的概念,所以它与DateTime 没有什么不同。我不知道你在哪里运行你的例子,但我在不同的 Ruby 版本中尝试了Time.new(2012,6,30,23,59,60,0),从 2.0 到 2.7,总是得到2012-07-01 00:00:00 +0000 @michau Time 是否支持闰秒取决于您的操作系统和时区配置。例如:TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)' => 2012-06-30 23:59:60 +0000TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)' => 2012-07-01 00:00:00 +0000 请注意,The Ruby Style Guide 现在声明 除非您需要考虑历史日历改革,否则不要使用 DateTime【参考方案3】:

我认为“有什么区别”的答案是 Ruby 标准库中这个问题的不幸常见答案之一:这两个类/库是由不同的人在不同的时间以不同的方式创建的。与精心策划的 Java 之类的开发相比,这是 Ruby 发展的社区性质的不幸后果之一。开发人员想要新功能,但又不想踩到现有的 API,所以他们只需要创建一个新类 - 对于最终用户来说,两者并没有明显的理由存在。

这对于一般的软件库来说是正确的:通常一些代码或 API 的原因是它被证明是历史的而不是逻辑的。

诱惑是从 DateTime 开始,因为它看起来更通用。日期...和时间,对吗?错误的。 Time 也可以更好地处理日期,实际上可以解析 DateTime 无法解析的时区。它的性能也更好。

我最终到处都在使用 Time。

不过,为了安全起见,我倾向于允许将 DateTime 参数传递到我的 Timey API 中,然后进行转换。另外,如果我知道两者都有我感兴趣的方法,我也接受,比如我为将时间转换为 XML(用于 XMLTV 文件)而编写的这种方法

# Will take a date time as a string or as a Time or DateTime object and
# format it appropriately for xmtlv. 
# For example, the 22nd of August, 2006 at 20 past midnight in the British Summertime
# timezone (i.e. GMT plus one hour for DST) gives: "20060822002000 +0100"
def self.format_date_time(date_time)
  if (date_time.respond_to?(:rfc822)) then
    return format_time(date_time)
  else 
    time = Time.parse(date_time.to_s)
    return format_time(time)
  end    
end

# Note must use a Time, not a String, nor a DateTime, nor Date.
# see format_date_time for the more general version
def self.format_time(time)
  # The timezone feature of DateTime doesn't work with parsed times for some reason
  # and the timezone of Time is verbose like "GMT Daylight Saving Time", so the only
  # way I've discovered of getting the timezone in the form "+0100" is to use 
  # Time.rfc822 and look at the last five chars
  return "#time.strftime( '%Y%m%d%H%M%S' ) #time.rfc822[-5..-1]"
end

【讨论】:

另外,Time.new 和 DateTime.new 处理时区的方式不同。我在 GMT+7,所以Time.new(2011, 11, 1, 10, 30) 产生2011-11-01 10:30:00 +0700DateTime.new(2011, 11, 1, 10, 30) 产生Tue, 01 Nov 2011 10:30:00 +0000 众所周知,精心策划的 Java 开发产生了完全简单的逻辑 API。 @PhươngNguyễn 嗨,任何想法如何/为什么会出现这种时区差异?它从哪里获得偏移量?【参考方案4】:

我发现使用 DateTime 可以更轻松地解析和计算不同时区一天的开始/结束之类的事情,假设您使用的是 ActiveSupport 扩展

在我的情况下,我需要根据我作为字符串收到的用户本地时间计算用户时区(任意)的一天结束时间,例如"2012-10-10 10:10 +0300"

使用 DateTime 就像

irb(main):034:0> DateTime.parse('2012-10-10 10:10 +0300').end_of_day
=> Wed, 10 Oct 2012 23:59:59 +0300
# it preserved the timezone +0300

现在让我们用同样的方式尝试一下 Time:

irb(main):035:0> Time.parse('2012-10-10 10:10 +0300').end_of_day
=> 2012-10-10 23:59:59 +0000
# the timezone got changed to the server's default UTC (+0000), 
# which is not what we want to see here.

其实Time在解析前需要知道时区(还要注意是Time.zone.parse,不是Time.parse):

irb(main):044:0> Time.zone = 'EET'
=> "EET"
irb(main):045:0> Time.zone.parse('2012-10-10 10:10 +0300').end_of_day
=> Wed, 10 Oct 2012 23:59:59 EEST +03:00

所以,在这种情况下,使用 DateTime 肯定更容易。

【讨论】:

现在在生产中使用它,该技术有什么缺点吗? DateTime 不考虑夏令时。因此,在节省时间更改时,它将无法正常工作。 DateTime.parse('2014-03-30 01:00:00 +0100').end_of_day 产生 Sun, 30 Mar 2014 23:59:59 +0100,但 Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_day 产生 Sun, 30 Mar 2014 23:59:59 CEST +02:00(CET=+01:00,CEST=+02:00 - 注意偏移量已更改)。但要做到这一点,您需要有关用户时区的更多信息(不仅仅是偏移量,还有是否使用节省时间)。 这可能是显而易见的,但Time.zone.parse 在解析不同区域的时间时非常有用 - 它迫使您考虑应该使用哪个区域。有时,Time.find_zone 效果更好。 @Petr''Bubák''Šedivý DateTime 确实解析了您给它的偏移量,即 +0100。您没有给Time 一个偏移量,而是一个时区(“CET”没有描述偏移量,它是一个时区的名称)。时区全年可以有不同的偏移量,但偏移量就是偏移量,并且始终相同。【参考方案5】:

考虑他们如何通过自定义实例以不同方式处理时区:

irb(main):001:0> Time.new(2016,9,1)
=> 2016-09-01 00:00:00 -0400
irb(main):002:0> DateTime.new(2016,9,1)
=> Thu, 01 Sep 2016 00:00:00 +0000
irb(main):003:0> Time.new(2016,9,1).to_i
=> 1472702400
irb(main):004:0> DateTime.new(2016,9,1).to_i
=> 1472688000

这在创建时间范围等时可能会很棘手。

【讨论】:

似乎只发生在纯红宝石(即没有 Rails)【参考方案6】:

除了the answer of Niels Ganser,你还可以考虑这个论点:

请注意,The Ruby Style Guide 非常清楚地表明了对此的立场:

无日期时间

除非您需要考虑历史日历,否则不要使用 DateTime 改革 - 如果你这样做,明确指定 start 参数 清楚地说明你的意图。

# bad - uses DateTime for current time
DateTime.now

# good - uses Time for current time
Time.now

# bad - uses DateTime for modern date
DateTime.iso8601('2016-06-29')

# good - uses Date for modern date
Date.iso8601('2016-06-29')

# good - uses DateTime with start argument for historical date
DateTime.iso8601('1751-04-23', Date::ENGLAND)

【讨论】:

【参考方案7】:

似乎在某些情况下行为非常不同:

Time.parse("Ends from 28 Jun 2018 12:00 BST").utc.to_s

“2018-06-28 09:00:00 UTC”

Date.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s

“2018-06-27 21:00:00 UTC”

DateTime.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s

“2018-06-28 11:00:00 UTC”

【讨论】:

这是一个有趣的观察,但应该解释它为什么会发生。 Date(不出所料)只解析日期,当 Date 转换为 Time 时,它总是使用本地时区的午夜作为时间。 TimeTimeDate 之间的区别源于 Time 不理解 BST 的事实,这令人惊讶,因为 Time 通常更正确地处理时区(例如,关于夏令时)。所以在这种情况下,只有DateTime 才能正确解析整个字符串。

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

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

雷林鹏分享:Ruby 日期 & 时间(Date & Time)

Ruby 中的日期时间解析

在 ruby​​ 中表示没有日期的时间

如何检查变量是不是是 Ruby 中的日期或时间或日期时间?

Ruby活动记录在mysql中插入日期时间而不是时间