DateTime和Ruby中的时间之间的差异


Answers:


177

较新版本的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可以处理Aristotle的博客文章。

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


6
所以我应该一直使用DateTime吗?
汤姆·雷曼

4
如果您正在处理日期,我会说使用DateTime。时间可以方便地表示诸如当前时间之类的东西,或者表示不久的将来的时间点,如10.minutes.from_now。两者之间有很多共同点,不过如前所述,DateTime可以代表更大范围的值。
tadman

3
我相信这是因为Ruby在遇到溢出时会从32位Fixnum切换到任意长度的Bignum。外部应用程序可能不支持该范围之外的数字。是的,在2038年之前,我们基本上都搞砸了,直到我们都同意适当的64位时间格式。陪审团仍在。
塔德曼,2012年

27
该答案早于1.9.2。忽略有关Time posix限制的所有说明,并根据Time和DateTime的API进行选择。
本·纳吉

8
这个答案已经过时了吧?现在建议使用Time或Rail的ActiveSupport :: WithTimeZone。您不再需要使用DateTime。它在那里是为了向后兼容。
Donato

102

[2018年7月编辑]

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

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

之前在此主题中未注意到的是以下几项优点之一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

使用较大值的唯一结果应该是性能,这在使用Integers(vs。s Bignum(超出Integer范围的值)或Rationals(跟踪纳秒)时更好):

从Ruby 1.9.2开始,Time实现使用带符号的63位整数Bignum或Rational。整数是自纪元以来的纳秒数,可以表示1823-11-12至2116-02-20。当使用Bignum或Rational时(在1823之前,2116之后,在纳秒以下),时间的使用速度比使用整数时慢。(http://www.ruby-doc.org/core-2.1.0/Time.html

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

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

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

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

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,操作系统需要支持leap秒,并且需要正确设置时区信息,例如通过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年仍然如此吗?
Qqwy

2
在Ruby 2.5.1中仍然如此。ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html:“DateTime不考虑任何leap秒,也不跟踪任何夏令时规则。” 但是,请注意,当您需要处理格里高里历之前的日期/时间时,DateTime具有优势。之前在此未提及,但在参考文档中进行了记录:“ […] Ruby的Time类实现了多功的格里高利历,并且没有日历改革的概念[…]。” 我将编辑答案以提供更多详细信息。
尼尔斯·甘瑟

Time也没有of秒的概念,因此与没什么不同DateTime。我不知道您在哪里运行示例,但是我尝试Time.new(2012,6,30,23,59,60,0)了从2.0到2.7的不同Ruby版本,并且始终使用2012-07-01 00:00:00 +0000
michau

@michau是否Time支持leap秒取决于您的操作系统和时区配置。例如: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
尼尔斯·甘瑟

@NielsGanser太好了,谢谢!我建议进行编辑以澄清这一点。
michau

44

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

通常对于软件库来说都是这样:通常是某些代码或API成为历史记录而不是逻辑记录的原因。

诱惑是从DateTime开始,因为它看起来更通用。日期...时间,对不对?错误。时间也会使日期更好,并且实际上可以解析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

8
此外,Time.new和DateTime.new处理时区的方式有所不同。我在GMT + 7上,因此Time.new(2011, 11, 1, 10, 30)产生的2011-11-01 10:30:00 +0700同时DateTime.new(2011, 11, 1, 10, 30)产生Tue, 01 Nov 2011 10:30:00 +0000
NguyễnPhương

21
众所周知,精心计划的Java开发产生了完全简单的逻辑API。
pje 2012年

@PhươngNguyễn:请补充一下答案,以便我投票赞成吗?这正是我决定选择Time over DateTime的原因。
2013年

@Senseful抱歉,直到现在我还没有收到您的消息。人们已经对我的评论给予了支持,所以我认为这很酷。
PHUONG阮

@PhươngNguyễn嗨,有什么想法会引起时区差异吗?它从哪里得到偏移量?
Joel_Blum

10

我发现,假设您使用的是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.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绝对容易。


1
现在在生产中使用此技术,该技术是否有不利之处?
Alex Moore-Niemi 2014年

1
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-注意偏移量已更改)。但是,为此,您需要有关用户时区的更多信息(不仅是偏移量,还包括是否使用节省时间)。
Petr'Bubák'Šedivý2015年

1
这可能是说明明显,但是Time.zone.parse非常有用的不同区域解析时候-它迫使你去思考哪个区域,你应该使用。有时,Time.find_zone的效果甚至更好。
prusswan

1
@Petr''Bubák''Šedivý DateTime确实解析了您给它的偏移量+0100。您没有提供Time偏移量,但是提供了时区(“ CET”没有描述偏移量,它是时区的名称)。时区在一年中可以有不同的偏移量,但是偏移量是一个偏移量,并且始终相同。
Mecki

4

考虑一下它们如何使用自定义实例来不同地处理时区:

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)
vemv

1

在某些情况下,行为似乎有很大不同:

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(毫不奇怪)仅解析日期,并且当将a Date转换Time为时,它将始终使用本地时区的午夜作为时间。之间的差TimeTimeDate从该事实茎Time不理解BST,这是令人惊讶的,因为时区通常由处理更正确地Time(相对于夏令时,例如)。因此,在这种情况下,只能DateTime正确解析整个字符串。
michau

1

除了Niels Ganser的答案,您还可以考虑以下说法:

请注意,《 Ruby样式指南》非常清楚地说明了这一点:

没有日期时间

除非需要考虑历史日历改革,否则不要使用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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.