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

219

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

Том Леман
источник
В документации есть раздел , объясняющий, когда использовать какой.
х-юри

Ответы:

177

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

В версиях, предшествующих Ruby 1.9, и во многих системах время представляется в виде 32-разрядного знакового значения, описывающего количество секунд с 1 января 1970 года по Гринвичу, тонкой обертки вокруг стандартного time_tзначения POSIX и ограниченного:

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 стандарта SQL. Они содержат произвольные даты и могут представлять практически любой момент времени, поскольку диапазон выражений обычно очень велик.

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

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

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

Тадман
источник
6
Так я должен всегда использовать DateTime?
Том Леман
4
Если вы работаете с датами, я бы сказал, использовать DateTime. Время удобно для представления таких вещей, как текущее время дня или точки в ближайшем будущем, такие как 10.minutes.from_now. Эти два имеют много общего, хотя, как отмечалось, DateTime может представлять гораздо более широкий диапазон значений.
tadman
3
Я полагаю, что это потому, что Ruby переключается на Bignum произвольной длины с 32-битного Fixnum, когда происходит переполнение. Числа вне этого диапазона могут не поддерживаться внешними приложениями. Да, в 2038 году мы в основном облажались, пока все не смогли договориться о правильном 64-битном формате времени. Юрий все еще отсутствует.
tadman
28
Этот ответ предшествует 1.9.2. Игнорируйте все, что говорится об ограничениях времени Posix, и сделайте свой выбор на основе API-интерфейсов Time и DateTime.
Бен Надь
8
Этот ответ устарел, верно? Теперь рекомендуется использовать ActiveSupport :: WithTimeZone от Time или Rail. Вам больше не нужно использовать DateTime. Это для обратной совместимости.
Донато
103

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

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

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

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

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

Справочная документация заканчивается рекомендацией использовать, 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

Единственным последствием использования больших значений должна быть производительность, которая лучше при использовании Integers (по сравнению Bignumс s (значения вне 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 не учитывает никаких скачков, не отслеживает правила летнего времени. ( 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, оно считается в днях.

Нильс Гансер
источник
Это все еще верно в 2018 году?
Qqwy
2
Это все еще верно в Ruby 2.5.1. ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html : «DateTime не учитывает никаких дополнительных секунд, не отслеживает правила летнего времени». Тем не менее, обратите внимание, что DateTime имеет преимущества, когда вам нужно иметь дело с датами / временем до григорианского календаря. Это не упоминалось здесь ранее, но задокументировано в справочной документации: «[…] Класс Time в Ruby реализует пролептический григорианский календарь и не имеет понятия о реформе календаря […]». Я отредактирую свой ответ, чтобы предоставить более подробную информацию.
Нильс Гансер
Timeтакже не имеет понятия о високосных секундах, поэтому он не отличается от DateTime. Я не знаю, где вы запускали свои примеры, но я пробовал Time.new(2012,6,30,23,59,60,0)в разных версиях Ruby, от 2.0 до 2.7, и всегда получал 2012-07-01 00:00:00 +0000.
Микау
@michau TimeПоддерживает ли високосные секунды или нет, зависит от вашей ОС и конфигурации часового пояса. Например: TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-06-30 23:59:60 +0000тогда как TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-07-01 00:00:00 +0000.
Нильс Гансер
@NielsGanser Отлично, спасибо! Я предложил изменить, чтобы прояснить этот момент.
Микау
44

Я думаю, что ответ на вопрос «в чем разница» является одним из неудачных общих ответов на этот вопрос в стандартных библиотеках Ruby: два класса / библиотеки были созданы по-разному разными людьми в разное время. Это одно из печальных последствий общественной природы эволюции Ruby по сравнению с тщательно спланированной разработкой чего-то вроде Java. Разработчики хотят новую функциональность, но не хотят наступать на существующие 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.
Phương Nguyễn
21
И, как мы все знаем, тщательно спланированная разработка Java привела к созданию исключительно простых логических API.
pje
@ PhươngNguyễn: Не могли бы вы добавить это в качестве ответа, чтобы я мог проголосовать за него? Именно поэтому я решил выбрать Time вместо DateTime.
Чувственный
@Senseful Извините, я не получил ваше сообщение до сих пор. Люди уже высказывают мои комментарии, поэтому я думаю, что это круто.
Phương Nguyễn
@ PhươngNguyễn Привет, есть идеи, как / почему эта разница во временных зонах? откуда берется смещение?
Joel_Blum
10

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

Теперь давайте попробуем то же самое со временем:

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

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
Используя это в производстве сейчас, есть ли недостатки в технике?
Алекс Мур-Ниеми
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 - обратите внимание, что смещение изменилось). Но для этого вам нужно больше информации о часовом поясе пользователя (не только о смещении, но и о том, используется ли экономия времени).
Петр Бубак Шедивы
1
Это может быть констатирую очевидное, но Time.zone.parseэто очень полезно при анализе раз с разными зонами - это заставляет вас думать о том, в какой зоне вы должны использовать. Иногда Time.find_zone работает даже лучше.
Пруссван
1
@ Petr''Bubák''Sedivý DateTimeпроанализировал смещение, которое вы ему дали, +0100. Вы не указали Timeсмещение, но часовой пояс («CET» не описывает смещение, это название часового пояса). Часовые пояса могут иметь разные смещения в течение года, но смещение - это смещение, и оно всегда одинаково.
Меки
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

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

gr8scott06
источник
Кажется, что это происходит только с обычным рубином (то есть без 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, он всегда использует полночь в местном часовом поясе в качестве времени. Разница между Timeи TimeDateсвязана с тем, что Timeне понимает BST, что удивительно, учитывая, что часовые пояса обычно обрабатываются более корректно Time(например, в отношении летнего времени). Таким образом, в этом случае, только DateTimeанализирует всю строку правильно.
Микау
1

В дополнение к ответу Нильса Гансера вы могли бы рассмотреть этот аргумент:

Обратите внимание, что в Руководстве по стилю 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)
Пол ван Леувен
источник