Разница между датой и временем в рубине

Какая разница между классами DateTime и Time в Ruby и какие факторы заставили бы меня выбрать один или другой?

Ответ 1

Более новые версии Ruby (2.0+) на самом деле не имеют существенных различий между этими двумя классами. Некоторые библиотеки будут использовать один или другой по историческим причинам, но новый код не обязательно должен быть затронут. Выбор одного для согласованности, вероятно, лучше всего, поэтому попробуйте и запишите то, что ожидают ваши библиотеки. Например, ActiveRecord предпочитает DateTime.

В версиях до Ruby 1.9 и во многих системах Время представлено как 32-разрядное знаковое значение, описывающее количество секунд с 1 января 1970 года 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, которая служит оболочкой вокруг стандартных полей DATETIME. Они содержат произвольные даты и могут представлять почти любой момент времени, поскольку диапазон выражения обычно очень велик.

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

Так что это обнадеживает, что DateTime может обрабатывать сообщения в блоге от Аристотеля.

При выборе одного, различия сейчас несколько субъективны. Исторически DateTime предоставил лучшие возможности для манипулирования им в календарном режиме, но многие из этих методов были перенесены на время, по крайней мере, в среду Rails.

Ответ 2

[Изменить июль 2018 года]

Все перечисленное ниже относится и к Ruby 2.5.1. Из справочной документации:

DateTime не учитывает никаких дополнительных секунд, не отслеживает правила летнего времени.

То, что не было отмечено в этой теме ранее, является одним из немногих преимуществ DateTime: он осведомлен о реформах календаря, тогда как Time - нет:

[…] Класс Ruby Time реализует пролептический григорианский календарь и не имеет понятия о реформе календаря […].

Справочная документация заканчивается рекомендацией использовать Time когда речь идет исключительно о датах/временах прошлого, текущего или будущего, и использовать DateTime только тогда, когда, например, день рождения Шекспира необходимо точно преобразовать: (выделение добавлено)

Итак, когда вы должны использовать DateTime в Ruby и когда вы должны использовать Time? Почти наверняка вы захотите использовать Time, поскольку ваше приложение, вероятно, имеет дело с текущими датами и временем. Однако, если вам нужно иметь дело с датами и временем в историческом контексте, вы захотите использовать DateTime […]. Если вам также приходится иметь дело с часовыми поясами, тогда удачи - просто имейте в виду, что вы, вероятно, будете иметь дело с местным солнечным временем, поскольку только в 19 веке введение железных дорог потребовало стандартного времени. и в конечном итоге часовые пояса.

[/Изменить июль 2018 года]

Начиная с ruby 2.0, большая часть информации в других ответах устарела.

В частности, Time сейчас практически не связано. Он может быть больше или меньше 63 бит от Epoch:

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 (значения вне диапазона Integer) или Rational (когда отслеживаются наносекунды)):

Начиная с 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 не учитывает никаких скачков, не отслеживает правила летнего времени. (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, оно считается в днях.

Ответ 3

Устаревшие! См. Ниже...

Разница в производительности не может быть подчеркнута достаточно... Время - C, а DateTime - Ruby:

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

Обновление (2/2012):

Как уже упоминалось в комментарии, 1.9.3 значительно улучшило производительность DateTime:

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

Ответ 4

Я думаю, что ответ на вопрос "какая разница" является одним из неудачных общих ответов на этот вопрос в стандартных библиотеках Ruby: два класса /lib создавались по-разному разными людьми в разное время. Это одно из неудачных последствий общинного характера эволюции Ruby по сравнению с тщательно спланированной разработкой чего-то вроде Java. Разработчики хотят новой функциональности, но не хотят наступать на существующие API, поэтому они просто создают новый класс - для конечного пользователя нет очевидной причины для существования этих двух.

Это верно для программных библиотек в целом: часто причина того, что какой-то код или API - это то, как он оказывается скорее историческим, чем логичным.

Искушение - начать с DateTime, потому что оно кажется более общим. Дата... и время, правильно? Неправильно. Время также делает даты лучше, и на самом деле может анализировать временные интервалы, где DateTime не может. Также он работает лучше.

Я повсюду использовал Time.

Чтобы быть в безопасности, я, как правило, допускаю, чтобы аргументы DateTime передавались в мои API-интерфейсы Timey, и либо конвертировали. Также, если я знаю, что у обоих есть метод, который меня интересует, я принимаю либо, как этот метод, который я написал для преобразования времени в 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

Ответ 5

Я нашел такие вещи, как синтаксический анализ и вычисление начала/конца дня в разных часовых поясах, легче сделать с 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

Теперь попробуем его так же, как и Time:

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 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 определенно проще работать.

Ответ 6

Рассмотрим, как они обрабатывают временные интервалы по-разному с пользовательскими экземплярами:

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

Это может быть сложно при создании временных диапазонов и т.д.

Ответ 7

Кажется, что в некоторых случаях поведение очень отличается:

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"