Ruby中DateTime和Time之间的区别

Difference between DateTime and Time in Ruby

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


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

在Ruby 1.9之前的版本中以及许多系统上,Time表示为一个32位有符号值,描述自1970年1月1日UTC以来的秒数,这是一个围绕POSIX标准time_t值的薄包装,并且有界:

1
2
3
4
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字段的包装器。这些包含任意日期,几乎可以表示任何时间点,因为表达范围通常非常大。

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

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

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


[编辑2018年7月]

以下所有内容在Ruby 2.5.1中仍然适用。从参考文档:

DateTime does not consider any leap seconds, does not track any summer time rules.

之前在此主题中没有注意到的是DateTime的少数优点之一:它知道日历改革,而Time不是:

[…] Ruby's Time class implements a proleptic Gregorian calendar and has no concept of calendar reform […].

参考文档最后建议在专门处理近期,当前或未来日期/时间时使用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开始,其他答案中的大部分信息都已过时。

特别是,Time现在几乎没有绑定。它可以比Epoch更多或更少甚至63位:

1
2
3
4
5
6
7
8
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时(相对于Bignum s(Integer范围之外的值)或Rational s(跟踪纳秒时)),性能更好:):

Since Ruby 1.9.2, Time implementation uses a signed 63 bit integer, Bignum or Rational. The integer is a number of nanoseconds since the Epoch which can represent 1823-11-12 to 2116-02-20. When Bignum or Rational is used (before 1823, after 2116, under nanosecond), Time works slower as when integer is used.
(http://www.ruby-doc.org/core-2.1.0/Time.html)

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

此外,应该注意两个先前未提及的DateTime限制:

DateTime does not consider any leapseconds, does not track any summer time rules.
(http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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偏移处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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添加数字时,它会以天为单位计算。


过时!见下文...

性能差异不够强调......时间是C,而DateTime是Ruby:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>> Benchmark.bm do |bm|
?>   bm.report('DateTime:') do
?>     n1 = DateTime.now
>>     n2 = DateTime.now
>>     1_000_000.times{ n1 < n2 }
>>   end
>>   bm.report('Time:    ') do
?>     n1 = Time.now
>>     n2 = Time.now
>>     1_000_000.times{ n1 < n2 }
>>   end
>> end
      user     system      total        real
DateTime:  4.980000   0.020000   5.000000 (  5.063963)
Time:      0.330000   0.000000   0.330000 (  0.335913)

更新(2012年2月):

正如评论中已经提到的,1.9.3已经大大提高了DateTime性能:

1
2
3
       user     system      total        real
DateTime:  0.330000   0.000000   0.330000 (  0.333869)
Time:      0.300000   0.000000   0.300000 (  0.306444)


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

对于一般的软件库来说也是如此:通常,某些代码或API的结果是历史而非逻辑。

诱惑是从DateTime开始,因为它似乎更通用。日期......和时间,对吧?错误。时间也更好地更新日期,实际上可以解析DateTime不能的时区。它也表现得更好。

我到处都在使用Time。

为了安全起见,我倾向于允许将DateTime参数传递给我的Timey API,并进行转换。另外,如果我知道两者都有我感兴趣的方法,我也接受,就像我写的用于将时间转换为XML(用于XMLTV文件)的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 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就像它一样简单

1
2
3
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

现在让我们以与时间相同的方式尝试:

1
2
3
4
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):

1
2
3
4
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肯定更容易。


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

1
2
3
4
5
6
7
8
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

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


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

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

"2018-06-28 09:00:00 UTC"

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

"2018-06-27 21:00:00 UTC"

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

"2018-06-28 11:00:00 UTC"