Konversi ke / dari DateTime dan Waktu di Ruby

132

Bagaimana Anda mengonversi antara objek DateTime dan Waktu di Ruby?

Hanya baca
sumber
1
Saya tidak yakin apakah ini harus menjadi pertanyaan yang terpisah, tetapi bagaimana Anda mengonversi antara Tanggal dan Waktu?
Andrew Grimm
8
Jawaban yang diterima dan diberi nilai tertinggi tidak lagi paling akurat di bawah Ruby versi modern. Lihat jawaban oleh @theTinMan dan oleh @PatrickMcKenzie di bawah ini.
Phrogz

Jawaban:

50

Anda akan membutuhkan dua konversi yang sedikit berbeda.

Untuk mengonversi dari Time ke DateTimeAnda dapat mengubah kelas Waktu sebagai berikut:

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

Penyesuaian serupa dengan Tanggal akan memungkinkan Anda mengonversi DateTime ke 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

Perhatikan bahwa Anda harus memilih antara waktu setempat dan waktu GM / UTC.

Cuplikan kode di atas diambil dari Ruby Cookbook O'Reilly . Kebijakan penggunaan kembali kode mereka memungkinkan ini.

Gordon Wilson
sumber
5
Ini akan berhenti pada 1.9 di mana DateTime # sec_fraction mengembalikan jumlah milidetik dalam satu detik. Untuk 1.9, Anda ingin menggunakan: 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)
anshul
sumber
13
+1 Ini mungkin bukan yang paling efisien dalam eksekusi, tetapi berfungsi, ringkas, dan sangat mudah dibaca.
Walt Jones
6
Sayangnya ini hanya benar-benar berfungsi ketika berhadapan dengan waktu setempat. Jika Anda mulai dengan DateTime atau Waktu dengan zona waktu yang berbeda, fungsi parse akan dikonversi menjadi zona waktu lokal. Anda pada dasarnya kehilangan zona waktu asli.
Bernard
6
Mulai dari ruby ​​1.9.1, DateTime.parse memang mempertahankan zona waktu. (Saya tidak memiliki akses ke versi sebelumnya.) Time.parse tidak mempertahankan zona waktu, karena itu mewakili time_t POSIX-standard, yang saya percaya adalah perbedaan bilangan bulat dari zaman. Setiap konversi ke Waktu harus memiliki perilaku yang sama.
anshul
1
Kamu benar. DateTime.parse bekerja di 1.9.1 tetapi tidak Time.parse. Bagaimanapun, ini lebih rentan kesalahan (konsisten) dan lebih cepat menggunakan DateTime.new (...) dan Time.new (..). Lihat jawaban saya untuk kode sampel.
Bernard
1
Hai @anshul. Saya tidak menyiratkan bahwa saya menyatakan :-). Info zona waktu tidak disimpan saat menggunakan Time.parse (). Mudah untuk diuji. Dalam kode Anda di atas, cukup ganti d = DateTime.now dengan d = DateTime.new (2010,01,01, 10,00,00, Rasional (-2, 24)). Sekarang akan menunjukkan tanggal d dikonversi ke zona waktu lokal Anda. Anda masih dapat melakukan aritmatika tanggal dan semua kecuali info asli hilang. Info ini adalah konteks untuk tanggal dan seringkali penting. Lihat di sini: stackoverflow.com/questions/279769/…
Bernard
63

Sebagai pembaruan ke keadaan ekosistem Ruby Date,, DateTimedan Timesekarang memiliki metode untuk mengkonversi antara berbagai kelas. Menggunakan 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
Manusia Timah
sumber
1
DateTime.to_time mengembalikan 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. Baru menyadari bahwa ini adalah masalah Ruby on Rails bukan masalah Ruby: stackoverflow.com/questions/11277454/… . Mereka bahkan memiliki bug yang diajukan terhadap metode ini di baris 2.x dan menandainya "tidak akan memperbaiki". IMHO keputusan yang mengerikan. Perilaku Rails benar-benar merusak antarmuka Ruby yang mendasarinya.
Jesse Clark
12

Sayangnya, DateTime.to_time, Time.to_datetimedan Time.parsefungsi tidak menyimpan info zona waktu. Semuanya dikonversi ke zona waktu lokal selama konversi. Date arithmetics masih berfungsi tetapi Anda tidak akan dapat menampilkan tanggal dengan zona waktu aslinya. Informasi konteks itu seringkali penting. Misalnya, jika saya ingin melihat transaksi dilakukan selama jam kerja di New York, saya mungkin lebih suka melihatnya ditampilkan dalam zona waktu semula, bukan zona waktu lokal saya di Australia (yang 12 jam lebih cepat dari New York).

Metode konversi di bawah ini menyimpan info tz itu.

Untuk Ruby 1.8, lihat jawaban Gordon Wilson . Ini dari Ruby Cookbook andal tua yang andal.

Untuk Ruby 1.9, ini sedikit lebih mudah.

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

Ini mencetak yang berikut ini

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

Info DateTime asli lengkap termasuk zona waktu disimpan.

Bernard
sumber
2
Waktu memang rumit, tetapi tidak ada alasan untuk tidak menyediakan konversi bawaan antara berbagai kelas waktu bawaan. Anda bisa melempar RangeException jika Anda mencoba untuk mendapatkan time_t UNIX untuk 4713 SM (meskipun nilai negatif BigNum akan lebih baik), tetapi setidaknya memberikan metode untuk itu.
Mark Reed
1
Time#to_datetimetampaknya melestarikan tz untuk saya:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00"
Phrogz
@Phrogz UTC offset tidak sama dengan zona waktu. Satu konstan, yang lain dapat berubah pada waktu yang berbeda tahun untuk menghemat waktu siang hari. DateTime tidak memiliki zona, ia mengabaikan DST. Waktu menghormatinya, tetapi hanya di TZ "lokal" (lingkungan sistem).
Andrew Vit
1

Memperbaiki solusi Gordon Wilson, ini adalah percobaan saya:

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

Anda akan mendapatkan waktu yang sama di UTC, kehilangan zona waktu (sayangnya)

Juga, jika Anda memiliki ruby ​​1.9, coba saja to_timemetode ini

Mildred
sumber
0

Saat melakukan konversi seperti itu, seseorang harus mempertimbangkan perilaku zona waktu saat mengkonversi dari satu objek ke objek lainnya. Saya menemukan beberapa catatan dan contoh yang bagus di posting stackoverflow ini .

kucing jantan
sumber