Разница между датой и временем в рубине
В чем разница между DateTime
а также Time
занятия на Ruby и какие факторы заставили бы меня выбрать один или другой?
7 ответов
Более новые версии Ruby (2.0+) на самом деле не имеют существенных различий между двумя классами. Некоторые библиотеки будут использовать одну или другую по историческим причинам, но новый код не обязательно должен быть связан с этим. Вероятно, лучше выбрать один для согласованности, поэтому постарайтесь совмещать с тем, что ожидают ваши библиотеки. Например, ActiveRecord предпочитает DateTime.
В версиях, предшествующих Ruby 1.9, и во многих системах время представляется как 32-разрядное знаковое значение, описывающее количество секунд с 1 января 1970 года по Гринвичу, тонкая оболочка вокруг стандарта 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 может обрабатывать сообщения в блоге от Аристотеля.
При выборе одного различия несколько субъективны. Исторически DateTime предоставлял лучшие варианты для манипулирования им в календарном режиме, но многие из этих методов были перенесены и на Time, по крайней мере, в среде Rails.
[Изменить июль 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
Единственным последствием использования больших значений должна быть производительность, которая лучше, когда Integer
s используются (против Bignum
s (значения за пределамиInteger
диапазон) илиRational
s (когда наносекунды отслеживаются)):
Начиная с 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)
Первый,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_i
=> 1341100823
Во-вторых,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>
Это может вызвать проблемы, когда времена вводятся как летнее время, а затем преобразуются в часовой пояс без летнего времени без отслеживания правильных смещений за пределами DateTime
сама по себе (многие операционные системы могут уже позаботиться об этом за вас).
В целом, я бы сказал, что в настоящее время Time
это лучший выбор для большинства приложений.
Также обратите внимание на важное различие при добавлении: когда вы добавляете число к объекту Time, оно считается в секундах, а когда вы добавляете число в DateTime, оно считается в днях.
Устаревшие! Увидеть ниже...
Разница в производительности не может быть подчеркнута достаточно... Время - 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)
Я думаю, что ответ на вопрос "в чем разница" является одним из неудачных общих ответов на этот вопрос в стандартных библиотеках Ruby: два класса / библиотеки были созданы по-разному разными людьми в разное время. Это одно из печальных последствий развития сообщества в Ruby по сравнению с тщательно спланированной разработкой чего-то вроде Java. Разработчики хотят новую функциональность, но не хотят наступать на существующие API, поэтому они просто создают новый класс - для конечного пользователя нет очевидных причин для существования этих двух.
Это верно для библиотек программного обеспечения в целом: часто причина того, что какой-то код или API-интерфейс заключается в том, что он оказывается историческим, а не логическим.
Соблазн - начать с DateTime, потому что он кажется более общим. Дата... и Время, верно? Неправильно. Время также делает даты лучше, и фактически может анализировать часовые пояса, где DateTime не может. Также это работает лучше.
Я закончил тем, что использовал Время везде.
Однако, чтобы быть в безопасности, я склонен разрешать передачу аргументов 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
Я обнаружил, что такие вещи, как разбор и вычисление начала / конца дня в разных часовых поясах, проще сделать с 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.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.
В дополнение к ответу Нильса Гансера вы можете рассмотреть этот аргумент:
Обратите внимание, что в Руководстве по стилю Ruby довольно четко изложена позиция по этому поводу:
Нет DateTime
Не используйте DateTime, если вам не нужно учитывать историческую реформу календаря - и если вы это делаете, явно укажите начальный аргумент, чтобы четко заявить о своих намерениях.
# 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)
Рассмотрим, как они обрабатывают часовые пояса по-разному с помощью пользовательских экземпляров
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
Это может быть сложно при создании временных диапазонов и т. Д.
Кажется, что в некоторых случаях поведение очень отличается:
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"