Перетворити в / з DateTime і час в Ruby


132

Як конвертувати між DateTime та об'єктом Time у Ruby?


1
Я не впевнений, чи має це бути окремим питанням, але як конвертувати між датою та часом?
Ендрю Грімм

8
У сучасних версіях Ruby прийняті та найвищі відповіді вже не є найбільш точними. Дивіться відповіді @theTinMan та @PatrickMcKenzie нижче.
Фрогз

Відповіді:


50

Вам знадобляться дві дещо різні конверсії.

Для перетворення Time до DateTimeвам може змінити клас часу наступним чином :

require 'date'
class Time
  def to_datetime
    # Convert seconds + microseconds into a fractional number of seconds
    seconds = sec + Rational(usec, 10**6)

    # Convert a UTC offset measured in minutes to one measured in a
    # fraction of a day.
    offset = Rational(utc_offset, 60 * 60 * 24)
    DateTime.new(year, month, day, hour, min, seconds, offset)
  end
end

Подібні коригування дати дозволить вам конвертувати DateTime в Time .

class Date
  def to_gm_time
    to_time(new_offset, :gm)
  end

  def to_local_time
    to_time(new_offset(DateTime.now.offset-offset), :local)
  end

  private
  def to_time(dest, method)
    #Convert a fraction of a day to a number of microseconds
    usec = (dest.sec_fraction * 60 * 60 * 24 * (10**6)).to_i
    Time.send(method, dest.year, dest.month, dest.day, dest.hour, dest.min,
              dest.sec, usec)
  end
end

Зауважте, що вам потрібно вибрати місцевий час та час GM / UTC.

Обидва вище фрагменти коду взяті з кулінарної книги O'Reilly . Політика їх повторного використання коду дозволяє це.


5
Це порушиться на 1.9, коли DateTime # sec_fraction поверне кількість мілісекунд за одну секунду. Для 1.9 ви хочете використовувати: usec = dest.sec_fraction * 10 ** 6
dkubb

185
require 'time'
require 'date'

t = Time.now
d = DateTime.now

dd = DateTime.parse(t.to_s)
tt = Time.parse(d.to_s)

13
+1 Це може бути не найефективнішим у виконанні, але воно працює, це лаконічно і дуже легко читається.
Уолт Джонс

6
На жаль, це справді спрацьовує лише в умовах місцевого часу. Якщо ви почнете з DateTime або Time з іншим часовим поясом, функція розбору перетвориться в локальний часовий пояс. Ви в основному втрачаєте початковий часовий пояс.
Бернар

6
Станом на рубін 1.9.1, DateTime.parse зберігає часовий пояс. (У мене немає доступу до більш ранніх версій.) Time.parse не зберігає часовий пояс, тому що він являє собою POSIX-стандарт time_t, який, на мою думку, є цілою різницею від епохи. Будь-яке перетворення у Час повинно мати таку саму поведінку.
anshul

1
Ти маєш рацію. DateTime.parse працює в 1.9.1, але не Time.parse. У будь-якому випадку, це менш схильні до помилок (послідовні) і, швидше за все, швидше використовувати DateTime.new (...) і Time.new (..). Дивіться мою відповідь щодо зразкового коду.
Бернар

1
Привіт @anshul. Я не маю на увазі, що заявляю :-). Інформація про часовий пояс не зберігається під час використання Time.parse (). Це легко перевірити. У своєму коді вище просто замініть d = DateTime.now на d = DateTime.new (2010,01,01, 10,00,00, Rational (-2, 24)). tt тепер покаже дату d, перетворену у ваш місцевий часовий пояс. Ви все ще можете виконувати арифметику дат, і все, окрім оригінальної інформації про tz, втрачено. Ця інформація є контекстом для дати та часто є важливою. Дивіться тут: stackoverflow.com/questions/279769/…
Бернард

63

Як оновлення стану екосистеми Ruby Date, DateTimeі Timeтепер є методи перетворення між різними класами. Використання Ruby 1.9.2+:

pry
[1] pry(main)> ts = 'Jan 1, 2000 12:01:01'
=> "Jan 1, 2000 12:01:01"
[2] pry(main)> require 'time'
=> true
[3] pry(main)> require 'date'
=> true
[4] pry(main)> ds = Date.parse(ts)
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[5] pry(main)> ds.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[6] pry(main)> ds.to_datetime
=> #<DateTime: 2000-01-01T00:00:00+00:00 (4903089/2,0,2299161)>
[7] pry(main)> ds.to_time
=> 2000-01-01 00:00:00 -0700
[8] pry(main)> ds.to_time.class
=> Time
[9] pry(main)> ds.to_datetime.class
=> DateTime
[10] pry(main)> ts = Time.parse(ts)
=> 2000-01-01 12:01:01 -0700
[11] pry(main)> ts.class
=> Time
[12] pry(main)> ts.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[13] pry(main)> ts.to_date.class
=> Date
[14] pry(main)> ts.to_datetime
=> #<DateTime: 2000-01-01T12:01:01-07:00 (211813513261/86400,-7/24,2299161)>
[15] pry(main)> ts.to_datetime.class
=> DateTime

1
DateTime.to_time повертає DateTime ... 1.9.3p327 :007 > ts = '2000-01-01 12:01:01 -0700' => "2000-01-01 12:01:01 -0700" 1.9.3p327 :009 > dt = ts.to_datetime => Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :010 > dt.to_time => Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :011 > dt.to_time.class => DateTime
Джессі Кларк

На жаль Щойно зрозумів, що це проблема Ruby on Rails, а не тема Ruby: stackoverflow.com/questions/11277454/… . У них навіть була помилка, подана проти цього методу, у рядку 2.x, і було зазначено, що "не виправить" Жахливе рішення ІМХО. Поведінка Rails повністю порушує базовий інтерфейс Ruby.
Джессі Кларк

12

На жаль, DateTime.to_time, Time.to_datetimeі Time.parseфункції не зберігають інформацію про часовий пояс. Під час конверсії все перетворюється на локальний часовий пояс. Арифметика дат все ще працює, але ви не зможете відображати дати з початковими часовими поясами. Ця інформація про контекст часто важлива. Наприклад, якщо я хочу побачити трансакції, здійснені в робочий час у Нью-Йорку, я, мабуть, вважаю за краще, щоб вони відображалися в оригінальних часових поясах, а не в моєму місцевому часовому поясі в Австралії (що на 12 годин попереду Нью-Йорка).

Наведені нижче методи перетворення зберігають цю інформацію про tz.

Щодо Ruby 1.8, подивіться відповідь Гордона Вілсона . Це із старої доброї надійної кулінарної книги Ruby.

Для Ruby 1.9 це трохи простіше.

require 'date'

# Create a date in some foreign time zone (middle of the Atlantic)
d = DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24))
puts d

# Convert DateTime to Time, keeping the original timezone
t = Time.new(d.year, d.month, d.day, d.hour, d.min, d.sec, d.zone)
puts t

# Convert Time to DateTime, keeping the original timezone
d = DateTime.new(t.year, t.month, t.day, t.hour, t.min, t.sec, Rational(t.gmt_offset / 3600, 24))
puts d

Це друкує наступне

2010-01-01T10:00:00-02:00
2010-01-01 10:00:00 -0200
2010-01-01T10:00:00-02:00

Повна оригінальна інформація про DateTime, включаючи часовий пояс, зберігається.


2
Час складний, але немає приводу, щоб не забезпечити вбудовану конверсію між різними вбудованими часовими класами. Ви можете кинути RangeException, якщо спробуєте отримати UNIX time_t за 4713 до н.е. (хоча негативне значення BigNum було б приємніше), але принаймні надайте метод для цього.
Марк Рід

1
Time#to_datetimeЗдається, збереже тз для мене:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00"
Фрогз

@Phrogz Зсув UTC - не те саме, що часовий пояс. Одне є постійним, інше може змінюватися в різний час року для літнього часу. У DateTime немає зони, вона ігнорує DST. Час його поважає, але лише в "локальному" (системному середовищі) ТЗ.
Ендрю Віт

1

Удосконалюється рішення Gordon Wilson, ось моя спроба:

def to_time
  #Convert a fraction of a day to a number of microseconds
  usec = (sec_fraction * 60 * 60 * 24 * (10**6)).to_i
  t = Time.gm(year, month, day, hour, min, sec, usec)
  t - offset.abs.div(SECONDS_IN_DAY)
end

Ви отримаєте той самий час у UTC, втрачаючи часовий пояс (на жаль)

Крім того, якщо у вас є рубін 1.9, просто спробуйте to_timeметод


0

Здійснюючи такі перетворення, слід враховувати поведінку часових поясів під час перетворення з одного об'єкта на інший. Я знайшов кілька хороших записок та прикладів у цій публікації про stackoverflow .

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