Відповіді:
Новіші версії Ruby (2.0+) насправді не мають суттєвих відмінностей між двома класами. Деякі бібліотеки використовуватимуть те чи інше з історичних причин, однак новий код не обов'язково повинен стосуватися. Вибір одного для узгодженості, мабуть, найкращий, тому спробуйте поговорити з тим, що очікують ваші бібліотеки. Наприклад, ActiveRecord віддає перевагу DateTime.
У версіях до Ruby 1.9 та в багатьох системах час представлений у вигляді 32-бітного підписаного значення, що описує кількість секунд з 1 січня 1970 року за UTC, тонкою обгорткою навколо стандартного 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.
[Редагувати липень 2018]
Все вищезазначене досі справедливо в Ruby 2.5.1. З довідкової документації :
DateTime не враховує жодних високосних секунд, не відстежує жодних літніх правил часу.
Те, що раніше не зазначалося в цій темі, є однією з небагатьох переваг DateTime
: вона обізнана з календарними реформами, а Time
не:
[…] Часовий клас Рубі реалізує пролептичний григоріанський календар і не має концепції реформи календаря […].
Довідкова документація завершується рекомендацією використовувати, Time
коли стосується виключно минулих, поточних чи майбутніх дат / часів, і використовувати лише DateTime
тоді, коли, наприклад, день народження Шекспіра потрібно точно перетворити: (наголос додано)
Отже, коли слід використовувати DateTime в Ruby і коли слід використовувати 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
s ( проти s (значення поза Integer
діапазону) або Rational
s (коли наносекунди відстежуються)):
Починаючи з Ruby 1.9.2, впровадження часу використовує підписане 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, воно рахується в днях.
Time
також не має поняття високосних секунд, тому воно не відрізняється від DateTime
. Я не знаю, де ви запускали ваші приклади, але я намагався Time.new(2012,6,30,23,59,60,0)
в різних версіях Ruby, від 2,0 до 2,7, і завжди потрапляв 2012-07-01 00:00:00 +0000
.
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
.
Я думаю, що відповідь на питання "в чому різниця" є однією з нещасних поширених відповідей на це питання в стандартних бібліотеках Рубі: два класи / лібрі були створені різними людьми в різний час. Це одне з прикрих наслідків спільноти еволюції Рубі порівняно з ретельно спланованою розробкою чогось на кшталт Java. Розробники хочуть нової функціональності, але не хочуть наступати на існуючі API, тому вони просто створюють новий клас - для кінцевого користувача немає очевидних причин існування двох.
Це справедливо для бібліотек програмного забезпечення в цілому: часто причина того, що якийсь код або API полягає в тому, що він виявляється історичним, а не логічним.
Спокуса почати з DateTime, тому що це здається більш загальним. Дата ... і час, правда? Неправильно. Час також покращує дати, і насправді можна проаналізувати часові пояси, де DateTime не може. Крім того, вона працює краще.
Я зрештою використовую Час скрізь.
Однак для забезпечення безпеки я схильний дозволити передачу аргументів DateTime у мої тимчасові 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
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
.
Я знайшов такі речі, як розбір і обчислення початку / кінця дня в різних часових поясах, що простіше зробити з 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'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 точно простіше.
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 - зауважте, що зміщення змінено). Але для цього вам потрібно більше інформації про часовий пояс користувача (не тільки компенсувати, а й те, чи використовується час).
Time.zone.parse
це дуже корисно при аналізі раз з різними зонами - це змушує вас думати про те, в якій зоні ви повинні використовувати. Іноді Time.find_zone працює ще краще.
DateTime
проаналізував зсув, який ви дали йому, що було +0100. Ви не дали Time
зміщення, але часовий пояс ("CET" не описує зміщення, це назва часового поясу). Часові зони можуть мати різний зміщення протягом року, але зміщення - це зміщення, і воно завжди однакове.
Розглянемо, як вони по-різному обробляють часові пояси за допомогою спеціальних даних:
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"
Date
(не дивно) розбирає лише дати, і коли Date
конвертується в Time
, він завжди використовує опівночі в локальному часовому поясі як час. Різниця між Time
та TimeDate
пов'язана з тим, що Time
BST не розуміє, що дивно, враховуючи, що з часовими поясами, як правило, керується правильніше Time
(наприклад, щодо літнього часу, наприклад). Тож у цьому випадку лише DateTime
правильно аналізує весь рядок.
Окрім відповіді Нільса Гансера, ви можете врахувати цей аргумент:
Зауважте, що керівництво по стилю Ruby досить чітко викладає позицію з цього приводу:
Немає дати
Не використовуйте 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)