Різниця між DateTime і Часом у Ruby


219

Яка різниця між класами DateTimeта Timeкласами у Ruby та які чинники змусять мене вибрати один чи інший?


Документи мають розділ , в якому пояснюється, коли використовувати.
x-yuri

Відповіді:


177

Новіші версії 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.


6
Тож чи слід завжди використовувати DateTime?
Том Леман

4
Якщо ви працюєте з датами, я б сказав, що використовуйте DateTime. Час є зручним для представлення таких речей, як поточний час доби, або пункти найближчим часом, такі як 10.minutes.from_now. Ці два мають багато спільного, хоча, як зазначалося, DateTime може представляти значно ширший діапазон значень.
тадман

3
Я вважаю, це тому, що Ruby переходить на Bignum довільної довжини з 32-розрядного Fixnum, коли відчуває переповнення. Зовнішні програми не можуть підтримувати номери поза цим діапазоном. Так, у 2038 році ми в основному накручені, поки ми не зможемо узгодити правильний 64-бітний формат часу. Журі все ще виходить.
тадман

28
Ця відповідь передує 1.9.2. Ігноруйте все, що йдеться про обмеження позиції часу, і зробіть свій вибір, виходячи з API часу та DateTime.
Бен Надь

8
Ця відповідь застаріла? Тепер рекомендується використовувати ActiveSupport :: Time або Rail :: WithTimeZone. Більше не потрібно використовувати DateTime. Саме там для зворотної сумісності.
Донато

103

[Редагувати липень 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використанні Bignums ( проти s (значення поза Integerдіапазону) або Rationals (коли наносекунди відстежуються)):

Починаючи з 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, воно рахується в днях.


Це все-таки правда у 2018 році?
Qqwy

2
Це все ще вірно в Ruby 2.5.1. ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html : "DateTime не враховує жодних високосних секунд, не відстежує жодних правил літнього часу." Однак зауважте, що DateTime має переваги, коли вам потрібно мати справу з догригоріанськими датами / часом календаря. Про це раніше не згадувалося, але це зафіксовано у довідковій документації: "[...] Часовий клас Рубі реалізує пролептичний григоріанський календар і не має концепції реформи календаря [...]." Я відредагую свою відповідь, щоб надати ще детальну інформацію.
Нільс Гансер

Timeтакож не має поняття високосних секунд, тому воно не відрізняється від DateTime. Я не знаю, де ви запускали ваші приклади, але я намагався Time.new(2012,6,30,23,59,60,0)в різних версіях Ruby, від 2,0 до 2,7, і завжди потрапляв 2012-07-01 00:00:00 +0000.
michau

@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 Чудово, дякую! Я запропонував редагувати, щоб уточнити цей момент.
michau

44

Я думаю, що відповідь на питання "в чому різниця" є однією з нещасних поширених відповідей на це питання в стандартних бібліотеках Рубі: два класи / лібрі були створені різними людьми в різний час. Це одне з прикрих наслідків спільноти еволюції Рубі порівняно з ретельно спланованою розробкою чогось на кшталт 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

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.
пн

@ PhươngNguyễn: Будь ласка, додайте це як відповідь, щоб я міг його проголосувати? Саме тому я вирішив обрати Час за 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

Тепер спробуємо так само, як і 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 точно простіше.


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 - зауважте, що зміщення змінено). Але для цього вам потрібно більше інформації про часовий пояс користувача (не тільки компенсувати, а й те, чи використовується час).
Петро '' Бубак '' Шедевi

1
Це може бути констатую очевидне, але Time.zone.parseце дуже корисно при аналізі раз з різними зонами - це змушує вас думати про те, в якій зоні ви повинні використовувати. Іноді Time.find_zone працює ще краще.
prusswan

1
@ Petr''Bubák''Šedivý 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

Це може бути складним при створенні діапазонів часу тощо.


Тільки, здається, трапляється з звичайним рубіном (тобто немає
рейликів

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(не дивно) розбирає лише дати, і коли Dateконвертується в Time, він завжди використовує опівночі в локальному часовому поясі як час. Різниця між Timeта TimeDateпов'язана з тим, що TimeBST не розуміє, що дивно, враховуючи, що з часовими поясами, як правило, керується правильніше Time(наприклад, щодо літнього часу, наприклад). Тож у цьому випадку лише DateTimeправильно аналізує весь рядок.
michau

1

Окрім відповіді Нільса Гансера, ви можете врахувати цей аргумент:

Зауважте, що керівництво по стилю 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.