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


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

在Ruby 1.9之前的版本和许多系统上,Time表示为一个32位带符号的值,描述了自1970年UTC 1月1日以来的秒数,这是一个围绕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环境中是这样。


I think the answer to "what's the difference" is one of the unfortunate common answers to this question in the Ruby standard libraries: the two classes/libs were created differently by different people at different times. It's one of the unfortunate consequences of the community nature of Ruby's evolution compared to carefully planned development of something like Java. Developers want new functionality but don't want to step on existing APIs so they just create a new class - to the end user there's no obvious reason for the two to exist.

一般来说,对于软件库来说,这是正确的:一些代码或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

我发现,如果使用ActiveSupport扩展,使用DateTime可以更容易地解析和计算不同时区的一天开始/结束时间。

在我的情况下,我需要计算一天的结束在一个用户的时区(任意)基于用户的本地时间,我接收到一个字符串,例如。"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。解析,而不是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:

So when should you use DateTime in Ruby and when should you use Time? Almost certainly you'll want to use Time since your app is probably dealing with current dates and times. However, if you need to deal with dates and times in a historical context you'll want to use DateTime […]. If you also have to deal with timezones then best of luck - just bear in mind that you'll probably be dealing with local solar times, since it wasn't until the 19th century that the introduction of the railways necessitated the need for Standard Time and eventually timezones.

[/编辑2018年7月]

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

特别是,时间现在实际上是不受约束的。它与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时性能会更好(相对于bigums(在Integer范围之外的值)或Rationals(当跟踪纳秒时):

从Ruby 1.9.2开始,Time实现使用有符号的63位整数,bigum或Rational。整数是从Epoch开始的纳秒数,可以表示1823-11-12到2116-02-20。当使用bigignum或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添加数字时,它以天为单位计算。


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

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风格指南非常清楚地说明了这一点:

No DateTime Don’t use DateTime unless you need to account for historical calendar reform - and if you do, explicitly specify the start argument to clearly state your intentions. # 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)