Ruby中DateTime和Time的区别

Ruby中DateTime类和Time类的区别是什么?是什么因素导致我选择其中一个?

60987 次浏览

Ruby的新版本(2.0+)在这两个类之间并没有明显的区别。有些库会因为历史原因使用其中一种,但新代码不一定需要考虑。选择一个一致性可能是最好的,所以尽量与你的库期望的相匹配。例如,ActiveRecord更喜欢DateTime。

在Ruby 1.9之前的版本和许多系统上,Time表示为一个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环境中是这样。

我认为“有什么不同”的答案是Ruby标准库中这个问题的一个不幸的常见答案:这两个类/库是由不同的人在不同的时间创建的。这是Ruby进化的社区特性带来的不幸后果之一,而不是像Java这样经过精心规划的开发。开发人员想要新的功能,但不想践踏现有的api,所以他们只是创建了一个新类——对于最终用户来说,这两个类的存在没有明显的理由。

一般来说,对于软件库来说,这是正确的:一些代码或API之所以是这样,往往是历史原因,而不是逻辑原因。

我们倾向于从DateTime开始,因为它看起来更通用。日期……还有时间,对吧?错了。Time在日期方面也做得更好,实际上它可以解析DateTime不能解析的时区。它的性能也更好。

我在任何地方都使用时间。

不过为了安全起见,我倾向于允许将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

我发现像解析和计算不同时区一天的开始/结束这样的事情用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

现在让我们以同样的方式来尝试《时间》:

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显然更容易。

[编辑2018年7月]

在Ruby 2.5.1中,所有这些仍然是正确的。从参考文档:

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

之前在这个线程中没有注意到的是DateTime的少数优点之一:它知道日历改革,而Time没有:

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

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

那么在Ruby中什么时候应该使用DateTime,什么时候应该使用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

使用较大值的唯一结果应该是性能,当使用Integer时性能会更好(相对于Bignums(在Integer范围之外的值)或Rationals(当跟踪纳秒时)):

从Ruby 1.9.2开始,Time实现使用有符号的63位整数,Bignum或Rational。整数是从Epoch开始的纳秒数,可以表示1823-11-12到2116-02-20。当使用bigignum或Rational时(1823年以前,2116年以后,纳秒以下),Time的运行速度比使用整数时慢。 (http://www.ruby-doc.org/core-2.1.0/Time.html) < / p >

换句话说,据我所知,__ABC0不再覆盖比Time更大范围的潜在值

此外,应该注意DateTime前面没有提到的两个限制:

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

首先,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添加数字时,它以天为单位计算。

考虑一下它们在自定义实例化时处理时区的不同方式:

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

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

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

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”

除了尼尔斯·甘瑟的答案之外,你还可以考虑以下参数:

请注意,Ruby风格指南非常清楚地声明了对此的立场:

没有DateTime

不要使用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)