Konwertuj do / z DateTime i Time w Rubim

133

Jak przekonwertować obiekt DateTime i Time w Rubim?

Tylko czytać
źródło
1
Nie jestem pewien, czy to powinno być osobne pytanie, ale jak przekonwertować datę i godzinę?
Andrew Grimm,
8
Zaakceptowane i najwyżej ocenione odpowiedzi nie są już najdokładniejsze w nowoczesnych wersjach Rubiego. Zobacz odpowiedzi @theTinMan i @PatrickMcKenzie poniżej.
Phrogz

Odpowiedzi:

51

Będziesz potrzebować dwóch nieco różnych konwersji.

Aby przekonwertować z Time na DateTime, możesz zmienić klasę czasu w następujący sposób:

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

Podobne korekty do daty umożliwiają konwersję DateTime do formatu 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

Pamiętaj, że musisz wybrać między czasem lokalnym a czasem GM / UTC.

Oba powyższe fragmenty kodu pochodzą z Ruby Cookbook O'Reilly . Na to zezwala ich polityka ponownego wykorzystywania kodu .

Gordon Wilson
źródło
5
Spowoduje to przerwanie w 1,9, gdzie DateTime # sec_fraction zwraca liczbę milisekund w ciągu jednej sekundy. Dla 1.9, którego chcesz użyć: usec = dest.sec_fraction * 10 ** 6
dkubb
186
require 'time'
require 'date'

t = Time.now
d = DateTime.now

dd = DateTime.parse(t.to_s)
tt = Time.parse(d.to_s)
anshul
źródło
13
+1 Może nie jest to najbardziej wydajne w wykonaniu, ale działa, jest zwięzłe i bardzo czytelne.
Walt Jones,
6
Niestety to naprawdę działa tylko w przypadku lokalnych czasów. Jeśli zaczniesz od DateTime lub Time z inną strefą czasową, funkcja analizy zostanie przekonwertowana na lokalną strefę czasową. Zasadniczo tracisz pierwotną strefę czasową.
Bernard
6
Począwszy od ruby ​​1.9.1, DateTime.parse zachowuje strefę czasową. (Nie mam dostępu do wcześniejszych wersji.) Time.parse nie zachowuje strefy czasowej, ponieważ reprezentuje standard POSIX time_t, co moim zdaniem jest całkowitą różnicą w stosunku do epoki. Każda konwersja na Time powinna mieć takie samo zachowanie.
anshul
1
Masz rację. DateTime.parse działa w wersji 1.9.1, ale nie Time.parse. W każdym razie mniej podatne na błędy (spójne) i prawdopodobnie szybsze jest użycie DateTime.new (...) i Time.new (..). Zobacz moją odpowiedź na przykładowy kod.
Bernard
1
Cześć @anshul. Nie sugeruję, że stwierdzam :-). Informacje o strefie czasowej nie są przechowywane podczas korzystania z Time.parse (). To łatwe do przetestowania. W powyższym kodzie po prostu zamień d = DateTime.now na d = DateTime.new (2010,01,01, 10,00,00, Rational (-2, 24)). tt pokaże teraz datę d przekonwertowaną na lokalną strefę czasową. Nadal możesz wykonywać arytmetykę na datach i wszystkie oprócz oryginalnych informacji tz zostaną utracone. Ta informacja jest kontekstem dla daty i często jest ważna. Zobacz tutaj: stackoverflow.com/questions/279769/...
Bernard
63

Jako aktualizacja stanu ekosystemu Ruby Date, DateTimea Timeteraz mamy metody do konwersji między różnymi klasami. Korzystanie z 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
Blaszany Człowiek
źródło
1
DateTime.to_time zwraca 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
Jesse Clark
Ups. Właśnie zdałem sobie sprawę, że jest to problem Ruby on Rails, a nie problem Rubiego: stackoverflow.com/questions/11277454/… . Mieli nawet zgłoszony błąd dotyczący tej metody w wierszu 2.x i oznaczyli go „nie da się naprawić”. Straszna decyzja IMHO. Zachowanie Railsów całkowicie łamie podstawowy interfejs Rubiego.
Jesse Clark
12

Niestety DateTime.to_time, Time.to_datetimeiTime.parse funkcje nie zachowują informacje strefy czasowej. Podczas konwersji wszystko jest konwertowane na lokalną strefę czasową. Arytmetyka dat nadal działa, ale nie będzie można wyświetlać dat w ich oryginalnych strefach czasowych. Ta informacja kontekstowa jest często ważna. Na przykład, jeśli chcę zobaczyć transakcje wykonane w godzinach pracy w Nowym Jorku, prawdopodobnie wolę, aby były wyświetlane w ich oryginalnych strefach czasowych, a nie w mojej lokalnej strefie czasowej w Australii (12 godzin przed Nowym Jorkiem).

Poniższe metody konwersji zachowują te informacje tz.

W przypadku Ruby 1.8 spójrz na odpowiedź Gordona Wilsona . Pochodzi ze starej, dobrej, niezawodnej książki kucharskiej Ruby.

W przypadku Rubiego 1.9 jest to nieco łatwiejsze.

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

Spowoduje to wydrukowanie następującego

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

Zachowywane są pełne oryginalne informacje dotyczące daty i godziny, w tym strefa czasowa.

Bernarda
źródło
2
Czas jest skomplikowany, ale nie ma wymówki, by nie zapewniać wbudowanej konwersji między różnymi wbudowanymi klasami czasu. Możesz zgłosić RangeException, jeśli spróbujesz uzyskać UNIX time_t dla 4713 BC (chociaż ujemna wartość BigNum byłaby ładniejsza), ale przynajmniej zapewnij odpowiednią metodę.
Mark Reed
1
Time#to_datetimewydaje się, że zachowuje tz dla mnie:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00"
Phrogz
@Phrogz Offset UTC to nie to samo, co strefa czasowa. Jedna jest stała, druga może zmieniać się o różnych porach roku na czas letni. DateTime nie ma strefy, ignoruje czas letni. Czas to szanuje, ale tylko w „lokalnym” (środowisku systemowym) TZ.
Andrew Vit
1

Ulepszając rozwiązanie Gordona Wilsona, oto moja próba:

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

Otrzymasz ten sam czas w UTC, tracąc strefę czasową (niestety)

Ponadto, jeśli masz Ruby 1.9, po prostu wypróbuj tę to_timemetodę

Mildred
źródło
0

Dokonując takich konwersji należy wziąć pod uwagę zachowanie stref czasowych podczas konwersji z jednego obiektu do drugiego. Znalazłem kilka dobrych notatek i przykładów w tym poście dotyczącym stackoverflow .

kocur
źródło