日期、时间和日期时间类是否必要?

34

有了能同时处理日期和时间的 DateTime 类,为什么还要有 DateTime 两个类呢?

5个回答

43

总结一下常见的 Ruby 时间类:

Time

这是基本的核心 Ruby 时间类。

  • 具有日期和时间属性(年、月、日、时、分、秒、亚秒)
  • 基于自 Unix 纪元(1970-01-01)以来的浮点秒间隔
  • 可以处理 Unix 纪元之前的负时间
  • 可以按秒为单位进行时间算术运算
  • 本地工作时区可以是 UTC 或“本地”(系统时区)

实际上,当涉及到处理时区时,有三种不同类型的 Time 对象,让我们看一个夏令时的例子来展示 DST:

utc = Time.utc(2012,6,1) # => 2012-12-21 00:00:00 UTC
utc.zone       # => "UTC"
utc.dst?       # => false
utc.utc?       # => true
utc.utc_offset # => 0

local = Time.local(2012,6,1) # => 2012-06-01 00:00:00 -0700
local.zone       # => "PDT"
local.dst?       # => true
local.utc?       # => false
local.utc_offset # => -25200

nonlocal = Time.new(2012,6,1,0,0,0, "-07:00") # => 2012-06-01 00:00:00 -0700
nonlocal.zone       # => nil
nonlocal.dst?       # => false
nonlocal.utc?       # => false
nonlocal.utc_offset # => -25200
最后两种类型看起来很相似,但要注意:不应该对非本地时间进行算术运算。它仅仅是带有UTC偏移量而没有时区的时间,因此不知道夏令时的规则。在夏令时的边界上添加时间不会改变偏移量,并且所得到的时间将是错误的。
ActiveSupport::TimeWithZone:这个值得在这里提一下,因为它是你在Rails中使用的。与Time相同,另外还可以:
- 可以处理任何时区 - 尊重夏令时 - 可以在时区之间转换时间
如果ActiveSupport可用,我通常都使用它,因为它解决了所有时区问题。
日期(Date)只有日期属性(年、月、日),基于距离任意“零日”(-4712-01-01)的整天间隔的整数,可以处理以整天为单位的日期算术,在古代朱利安历和现代格里高利历之间转换。当你涉及到整天的时候,日期比时间(Time)更有用: 不必担心时区!
DateTime具有日期和时间属性(年、月、日、小时、分、秒),基于自任意“零日”(-4712-01-01)以来的整天间隔分数,可以处理以整天或分数为单位的日期算术。个人而言,我从来没有理由使用它: 它很慢,处理时间时不考虑时区,接口不一致。我发现当你假设你有一个像Time一样的对象,但实际上它的行为却像Date一样时,它会导致混淆。
Time.new(2012, 12, 31, 0, 0, 0) + 1 == Time.new(2012, 12, 31, 0, 0, 1)
DateTime.new(2012, 12, 31, 0, 0, 0) + 1 == DateTime.new(2013, 1, 1, 0, 0, 0)

此外,它具有毫无意义的“zone”属性(请注意非本地时间对象会警告您zone == nil),在将其转换为Time之前,您无法了解任何其他信息:

dt = DateTime.new(2012,12,6, 1, 0, 0, "-07:00")
dt.zone # => "-07:00"
dt.utc? # => NoMethodError: undefined method `utc?'
dt.dst? # => NoMethodError: undefined method `dst?'
dt.utc_offset # => NoMethodError: undefined method `utc_offset'

处理微秒来进行四舍五入的方式也有些奇怪。你可能会认为,由于它没有usec属性,它只涉及整数,但你会发现这是错误的:

DateTime.now.usec # => NoMethodError: undefined method `usec'
DateTime.now.to_time.usec => 629399

简而言之,除非你处理古代的天文事件并需要将朱利安日历(含时间)转换为现代日历,否则请不要使用DateTime。如果有人真正需要使用这个类,请在评论中留言。


1
今天我刚刚发现了DateTime的用途...一个日历/调度应用程序,允许用户创建重复预约,在跨越夏令时边界时应该出现在所选的时间。最简单的方法是使用一个不知道夏令时的类来实现。 - Alex D
@AlexD 我想指出的是,你可以使用“非本地”时间来做同样的事情,例如 Time.new(,,,,,,"-08:00"),它提供了一个UTC偏移量但忽略了夏令时。无论哪种情况,你都必须注意UTC偏移量在一年中有一半是错误的,因此与实际时间(如“现在”)进行比较可能是错误的。 - Andrew Vit
2
不要使用DateTime。此外,我在rubyconf 2012上进行了一次闪电演讲,主题是时区,当我查看我的笔记时看到了“TODO:Date vs. Time vs. DateTime”。https://gist.github.com/3668333 https://github.com/bf4/Notes/blob/master/talks_mine/time-lightning-rubyconf2012.pdf?raw=true http://vimeo.com/53892354 - BF4
3
如果从stdlib中require 'time',则会将Time#strptime添加到核心的Time类中。(不确定为什么会是这样。) - Andrew Vit
感谢Zachary Scott,现在Ruby源代码中有一个很好的例子来回答这个问题 https://github.com/ruby/ruby/commit/45458752d3f54ac81661d93aa55b8b5805f78138#diff-3991f2801546530eb23774963a10fa94R9476 (原始要点 https://gist.github.com/pixeltrix/e2298822dd89d854444b ) h/t http://weblog.rubyonrails.org/2015/6/6/this-week-in-rails-datetime-vs-time-summer-student-projects-and-more/ - BF4
显示剩余4条评论

28

我知道已有一个被接受的答案,但我有一些要补充的。Date类是一个重量级、学术性强的类。它可以处理各种RFC,解析最奇怪的东西,并将来自千年前的儒略日转换为所选的格里高利改革日期。Time类则是轻量级的,不知道这些东西。它更便宜,在基准测试中有所体现:

require 'benchmark'
require 'date'

Benchmark.bm(10) do |x|
  x.report('date'){100000.times{Date.today} }
  x.report('datetime'){100000.times{DateTime.now} }
  x.report('time'){100000.times{Time.now} }
end

结果:

                user     system      total        real
date        1.250000   0.270000   1.520000 (  1.799531)
datetime    6.660000   0.360000   7.020000 (  7.690016)
time        0.140000   0.030000   0.170000 (  0.200738)

(Ruby 1.9.2)


2
2.0版本中,日期和日期时间之间没有区别。 - Andrei Botalov

17

DateTimeDate的子类,因此您可以使用Date做的任何事情都可以使用DateTime完成。但是,正如tadman和steenslag指出的那样,DateTime速度较慢。有关它有多慢,请参见steenslag的答案。

关于DateTimeTime的区别,我在这里找到了一些内容here

时间是Unix纪元的包装器。 日期(和DateTime)使用有理数和“零日”进行存储。因此,时间更快,但上限和下限与时期时间绑定(对于32位时期时间而言,大约是1970-2040...而日期(和DateTime)具有几乎无限的范围,但速度非常慢。

简而言之,DateTime是全能巨星,并且通常应该优先使用,但如果想要最后一点优化,则使用Time可以提高性能。


4
“日期”也很有用,因为它没有任何相关的时区信息,这可以避免某些计算变得不必要地困难。 - tadman
1
@tadman 但是日期的值不也取决于时区吗? - sawa
2
日期并不关心时区,它甚至不知道时区是什么。将日期转换为日期时间或反之会使用时区,但您需要将其指定为默认设置或有意请求。 - tadman
3
一般情况下不建议使用DateTime。需要注意的是,DateTime仅仅是一个日期加上时间的属性,不能进行任何时间算术运算。它只涉及整天计算:DateTime.new(2012,12,31,0,0,0) + 1 == DateTime.new(2013,1,1) - Andrew Vit
4
这个答案是否过时了?自从Ruby 2发布之后,Time使用了63位来表示时间,并且可以覆盖几乎所有日期。此外,DateTime不包括闰秒(原子振荡和地球自转之间的差异),具有讽刺意味的是,这种差异将在2015年6月30日星期二发生。 - Donato

4
另一种思考方式是,DateDateTime以时钟和日历的形式来模拟时间,这对于向用户描述时间和安排事件非常有用。如果您不关心时间并且不想考虑时区,那么只使用Date而不涉及时间是很好的选择。 Time将时间建模为一个连续体,并包装Unix时间戳(仅为整数)。这对计算机内部应用程序非常有用,因为它们不太关心是否跨越了日历边界,而只关心经过了多少秒(或毫秒)。

-1

是的。Date仅处理某些日期,例如1989年3月31日。但它不处理时间,例如下午12:30。 DateTime可以同时处理日期和时间,例如1989年3月31日下午12:30 EST。

有时您不需要DateTime的所有部分。例如,您想知道用户何时注册了您的网站,这里使用Date就足够了,因为时间最终是无关紧要的。

在某些情况下,您可能只想要时间。例如,如果是午餐时间,您可能想告诉用户您的办公室已关闭。此时,日期是无关紧要的。

然而,在大多数情况下,使用DateTime,因为它可以用作日期、时间或两者兼备。


1
是的,我知道这个。非常明显。我的问题是为什么要为日期和时间分别创建类?为什么不只有DateTime呢?例如,您可以解析包含“7:30 PM EST”的字符串,并确定那将是一个时间,因此只需创建具有时间功能的DateTime实例即可。 - RyanScottLewis
一个long也可以作为整数使用,但你不想把所有的int都改成long,因为这会浪费空间,而且不是正确的工具。float也可以使用,但涉及到数学计算时需要更长的时间。 - Malfist
答案仍然是一样的。你应该使用最适合工作的工具。例如,以钳子为例,它们可以做任何事情,但几乎从来不是最适合工作的工具。 - Malfist
那就是我的问题的要点。如果已经有一个最好的工具(DateTime),为什么我们还要创建/使用其他“工具”(日期和时间)呢? - RyanScottLewis
2
时间不是TimeOfDay类,它是一个时间戳。请看我的回答。 - Austin Taylor
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接