Perbedaan antara DateTime dan Waktu di Ruby

218

Apa perbedaan antara DateTimedan Timekelas di Ruby dan faktor-faktor apa yang menyebabkan saya memilih satu atau yang lain?

Tom Lehman
sumber
Dokumen memiliki bagian , yang menjelaskan kapan harus menggunakan yang mana.
x-yuri

Jawaban:

177

Versi Ruby yang lebih baru (2.0+) tidak benar-benar memiliki perbedaan yang signifikan antara kedua kelas. Beberapa perpustakaan akan menggunakan satu atau yang lain karena alasan historis, tetapi kode baru tidak perlu diperhatikan. Memilih satu untuk konsistensi mungkin yang terbaik, jadi cobalah dan sesuaikan dengan apa yang perpustakaan Anda harapkan. Misalnya, ActiveRecord lebih suka DateTime.

Dalam versi sebelum Ruby 1.9 dan pada banyak sistem, Time direpresentasikan sebagai nilai yang ditandatangani 32-bit yang menggambarkan jumlah detik sejak 1 Januari 1970 UTC, pembungkus tipis di sekitar time_tnilai standar POSIX , dan terikat:

Time.at(0x7FFFFFFF)
# => Mon Jan 18 22:14:07 -0500 2038
Time.at(-0x7FFFFFFF)
# => Fri Dec 13 15:45:53 -0500 1901

Versi Ruby yang lebih baru dapat menangani nilai yang lebih besar tanpa menghasilkan kesalahan.

DateTime adalah pendekatan berbasis kalender di mana tahun, bulan, hari, jam, menit dan detik disimpan secara terpisah. Ini adalah konstruksi Ruby on Rails yang berfungsi sebagai pembungkus di sekitar bidang DATETIME standar SQL. Ini berisi tanggal yang berubah-ubah dan dapat mewakili hampir semua titik waktu karena rentang ekspresi biasanya sangat besar.

DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000

Jadi meyakinkan bahwa DateTime dapat menangani posting blog dari Aristoteles.

Saat memilih satu, perbedaannya agak subyektif sekarang. Secara historis DateTime telah menyediakan opsi yang lebih baik untuk memanipulasinya dengan cara kalender, tetapi banyak dari metode ini telah dipindahkan ke Time juga, setidaknya dalam lingkungan Rails.

anak laki-laki
sumber
6
Jadi haruskah saya selalu menggunakan DateTime?
Tom Lehman
4
Jika Anda bekerja dengan tanggal, saya akan mengatakan menggunakan DateTime. Waktu nyaman untuk mewakili hal-hal seperti waktu saat ini hari, atau poin dalam waktu dekat seperti 10.minutes.from_now. Keduanya memiliki banyak kesamaan, meskipun seperti yang dicatat DateTime dapat mewakili rentang nilai yang jauh lebih luas.
tadman
3
Saya percaya itu karena Ruby beralih ke Bignum panjang sewenang-wenang dari 32-bit Fixnum ketika mengalami overflow Angka di luar rentang itu mungkin tidak didukung oleh aplikasi eksternal. Ya, pada tahun 2038 kita pada dasarnya kacau sampai kita semua dapat menyetujui format waktu 64-bit yang tepat. Juri masih keluar.
tadman
28
Jawaban ini ada sebelum 1.9.2. Abaikan semua yang dikatakan tentang batasan waktu Posix, dan tentukan pilihan Anda berdasarkan API Waktu dan DateTime.
Ben Nagy
8
Jawaban ini sudah usang kan? Sekarang disarankan untuk menggunakan TimeSup ActiveSupport :: WithTimeZone. Anda tidak perlu lagi menggunakan DateTime. Itu ada untuk kompatibilitas ke belakang.
Donato
102

[Edit Juli 2018]

Semua hal di bawah ini masih berlaku di Ruby 2.5.1. Dari dokumentasi referensi :

DateTime tidak mempertimbangkan detik kabisat, tidak melacak aturan waktu musim panas.

Apa yang belum dicatat di utas ini sebelumnya adalah salah satu dari sedikit keuntungan DateTime: ia menyadari reformasi kalender sedangkan Timetidak:

[...] Kelas Waktu Ruby mengimplementasikan kalender Gregorian yang subur dan tidak memiliki konsep reformasi kalender [...].

Dokumentasi referensi diakhiri dengan rekomendasi untuk digunakan Timeketika secara eksklusif berurusan dengan tanggal / waktu dekat-masa lalu, saat ini atau masa depan dan hanya digunakan DateTimeketika, misalnya, ulang tahun Shakespeare perlu dikonversi secara akurat: (penekanan ditambahkan)

Jadi kapan Anda harus menggunakan DateTime di Ruby dan kapan Anda harus menggunakan Waktu? Hampir pasti Anda ingin menggunakan Waktu karena aplikasi Anda mungkin berurusan dengan tanggal dan waktu saat ini. Namun, jika Anda harus berurusan dengan tanggal dan waktu dalam konteks historis, Anda ingin menggunakan DateTime [...]. Jika Anda juga harus berurusan dengan zona waktu maka yang terbaik adalah keberuntungan - ingatlah bahwa Anda mungkin akan berhadapan dengan waktu matahari lokal, karena baru pada abad ke-19 pengenalan kereta api mengharuskan perlunya Waktu Standar. dan akhirnya zona waktu.

[/ Edit Juli 2018]

Pada ruby ​​2.0, sebagian besar informasi dalam jawaban lain sudah usang.

Secara khusus, Timesekarang praktis tidak terikat. Itu bisa lebih atau kurang dari 63 bit dari 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

Satu-satunya konsekuensi dari menggunakan nilai yang lebih besar adalah kinerja, yang lebih baik ketika Integers digunakan (vs Bignums (nilai di luar Integerkisaran) atau Rationals (ketika nanodetik dilacak)):

Sejak Ruby 1.9.2, implementasi waktu menggunakan integer 63 bit yang ditandatangani, Bignum atau Rasional. Bilangan bulat adalah sejumlah nanodetik sejak Zaman yang dapat mewakili 1823-11-12 hingga 2116-02-20. Ketika Bignum atau Rasional digunakan (sebelum 1823, setelah 2116, di bawah nanosecond), Waktu bekerja lebih lambat seperti ketika integer digunakan. ( http://www.ruby-doc.org/core-2.1.0/Time.html )

Dengan kata lain, sejauh yang saya mengerti, DateTimetidak lagi mencakup rentang nilai potensial yang lebih luas daripadaTime .

Selain itu, dua pembatasan yang sebelumnya tidak disebutkan DateTimemungkin harus dicatat:

DateTime tidak mempertimbangkan lompatan detik, tidak melacak aturan waktu musim panas. ( http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime )

Pertama, DateTimetidak memiliki konsep lompatan detik:

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

Agar contoh di atas dapat bekerja Time, OS perlu mendukung detik kabisat dan informasi zona waktu harus ditetapkan dengan benar, misalnya melalui TZ=right/UTC irb(pada banyak sistem Unix).

Kedua, DateTimememiliki pemahaman yang sangat terbatas tentang zona waktu dan khususnya tidak memiliki konsep penghematan siang hari . Ini cukup menangani zona waktu sesederhana offset 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>

Ini dapat menyebabkan masalah ketika waktu dimasukkan sebagai DST dan kemudian dikonversi menjadi zona waktu non-DST tanpa melacak offset yang benar di luar dari DateTimedirinya sendiri (banyak sistem operasi yang sebenarnya sudah menangani ini untuk Anda).

Secara keseluruhan, saya akan mengatakan bahwa saat ini Timeadalah pilihan yang lebih baik untuk sebagian besar aplikasi.

Perhatikan juga perbedaan penting pada penambahan: ketika Anda menambahkan angka ke objek Waktu, itu dihitung dalam hitungan detik, tetapi ketika Anda menambahkan nomor ke DateTime, itu dihitung dalam beberapa hari.

Niels Ganser
sumber
Apakah ini masih berlaku pada tahun 2018?
Qqwy
2
Itu masih benar di Ruby 2.5.1. ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html : "DateTime tidak mempertimbangkan detik kabisat, tidak melacak aturan waktu musim panas." Namun, perhatikan bahwa DateTime memiliki kelebihan ketika Anda harus berurusan dengan tanggal / waktu kalender pra-Gregorian. Ini tidak disebutkan di sini sebelumnya tetapi didokumentasikan dalam dokumentasi referensi: "[...] Kelas Waktu Ruby mengimplementasikan kalender Gregorian yang subur dan tidak memiliki konsep reformasi kalender [...]." Saya akan mengedit jawaban saya untuk memberikan lebih banyak detail.
Niels Ganser
Timetidak memiliki konsep detik kabisat, jadi tidak berbeda dari DateTime. Saya tidak tahu di mana Anda menjalankan contoh Anda, tetapi saya mencoba Time.new(2012,6,30,23,59,60,0)dalam versi Ruby yang berbeda, dari 2.0 hingga 2.7, dan selalu berhasil 2012-07-01 00:00:00 +0000.
michau
@michau Apakah Timemendukung lompatan detik atau tidak tergantung pada konfigurasi OS & zona waktu Anda. Misalnya: TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-06-30 23:59:60 +0000sedangkan TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-07-01 00:00:00 +0000.
Niels Ganser
@NielsGanser Hebat, terima kasih! Saya menyarankan edit untuk menjelaskan hal ini.
michau
44

Saya pikir jawaban untuk "apa bedanya" adalah salah satu jawaban umum yang disayangkan untuk pertanyaan ini di perpustakaan standar Ruby: dua kelas / lib dibuat secara berbeda oleh orang yang berbeda di waktu yang berbeda. Ini adalah salah satu konsekuensi malang dari sifat komunitas evolusi Ruby dibandingkan dengan pengembangan yang direncanakan dengan hati-hati seperti Jawa. Pengembang menginginkan fungsionalitas baru tetapi tidak ingin menginjak API yang ada sehingga mereka hanya membuat kelas baru - bagi pengguna akhir tidak ada alasan yang jelas untuk keduanya ada.

Ini berlaku untuk pustaka perangkat lunak secara umum: sering kali alasan beberapa kode atau API adalah cara itu ternyata lebih bersifat historis daripada logis.

Godaan akan dimulai dengan DateTime karena tampaknya lebih umum. Tanggal ... dan Waktu, kan? Salah. Waktu juga menentukan tanggal dengan lebih baik, dan pada kenyataannya dapat mem-parsing zona waktu di mana DateTime tidak bisa. Berkinerja lebih baik.

Saya akhirnya menggunakan Waktu di mana-mana.

Agar lebih aman, saya cenderung membiarkan argumen DateTime diteruskan ke API Timey saya, dan entah itu dikonversi. Juga jika saya tahu bahwa keduanya memiliki metode yang saya tertarik saya juga menerima, seperti metode ini saya menulis untuk mengkonversi kali ke XML (untuk file 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
Perkelahian
sumber
8
Juga, Time.new dan DateTime.new berhadapan dengan zona waktu secara berbeda. Saya menggunakan GMT + 7, jadi Time.new(2011, 11, 1, 10, 30)menghasilkan 2011-11-01 10:30:00 +0700sekaligus DateTime.new(2011, 11, 1, 10, 30)menghasilkan Tue, 01 Nov 2011 10:30:00 +0000.
Phương Nguyễn
21
Dan seperti yang kita semua tahu, pengembangan Java yang direncanakan dengan cermat menghasilkan API yang sederhana dan logis.
PT.
@ PhươngNguyễn: Maukah Anda menambahkannya sebagai jawaban agar saya dapat memilihnya? Itulah alasan saya memutuskan untuk memilih Waktu daripada DateTime.
Senseful
@Senseful Maaf saya belum menerima pesan Anda sampai sekarang. Orang-orang sudah memberikan beberapa komentar positif pada komentar saya, jadi saya pikir itu keren di sini.
Phương Nguyễn
@ PhươngNguyễn Hai, ada ide bagaimana / mengapa perbedaan zona waktu ini? dari mana mengambil offset?
Joel_Blum
10

Saya menemukan hal-hal seperti parsing dan menghitung awal / akhir hari di zona waktu yang berbeda lebih mudah dilakukan dengan DateTime, dengan asumsi Anda menggunakan ekstensi ActiveSupport .

Dalam kasus saya, saya perlu menghitung akhir hari di zona waktu pengguna (sewenang-wenang) berdasarkan waktu lokal pengguna yang saya terima sebagai string, misalnya "2012-10-10 10:10 +0300"

Dengan DateTime sesederhana itu

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

Sekarang mari kita coba dengan cara yang sama dengan 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.

Sebenarnya, Waktu perlu mengetahui zona waktu sebelum parsing (perhatikan juga Time.zone.parse, bukan 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

Jadi, dalam hal ini pasti lebih mudah menggunakan DateTime.

Yuriy Kharchenko
sumber
1
Menggunakan ini dalam produksi sekarang, apakah ada kelemahan teknik ini?
Alex Moore-Niemi
1
DateTime tidak memperhitungkan waktu musim panas. Dengan demikian menghemat waktu berubah, itu tidak akan berfungsi dengan benar. DateTime.parse('2014-03-30 01:00:00 +0100').end_of_daymenghasilkan Sun, 30 Mar 2014 23:59:59 +0100, tetapi Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_daymenghasilkan Sun, 30 Mar 2014 23:59:59 CEST +02:00(CET = + 01: 00, CEST = + 02: 00 - perhatikan offset berubah). Tetapi untuk melakukan ini, Anda memerlukan lebih banyak informasi tentang zona waktu pengguna (tidak hanya ofset tetapi juga apakah menghemat waktu digunakan).
Petr '' Bubák '' Šedivý
1
Ini mungkin menyatakan yang sudah jelas, tapi Time.zone.parseini sangat berguna ketika parsing kali dengan zona yang berbeda - memaksa Anda untuk berpikir tentang yang zona Anda harus menggunakan. Terkadang, Time.find_zone bekerja lebih baik.
Prusswan
1
@ Petr''Bubák''Šedivý DateTimemengurai offset yang Anda berikan, yaitu +0100. Anda tidak memberikan Timeoffset, tetapi zona waktu ("CET" tidak menggambarkan offset, itu adalah nama zona waktu). Zona waktu dapat memiliki offset yang berbeda sepanjang tahun, tetapi offset adalah offset dan selalu sama.
Mecki
4

Pertimbangkan bagaimana mereka menangani zona waktu secara berbeda dengan instantiasi khusus:

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

Ini bisa rumit ketika membuat rentang waktu, dll.

gr8scott06
sumber
Tampaknya hanya terjadi dengan ruby ​​biasa (yaitu tidak ada Rails)
vemv
1

Tampaknya dalam beberapa kasus perilaku ini sangat berbeda:

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"

Alexey Strizhak
sumber
Itu pengamatan yang menarik, tetapi harus dijelaskan mengapa itu terjadi. Date(Tidak mengherankan) hanya mengurai tanggal, dan ketika Datedikonversi menjadi Time, ia selalu menggunakan tengah malam di zona waktu lokal sebagai waktu. Perbedaan antara Timedan TimeDateberasal dari fakta yang Timetidak memahami BST, yang mengejutkan, mengingat bahwa zona waktu biasanya ditangani dengan lebih benar Time(misalnya berkaitan dengan waktu musim panas, misalnya). Jadi dalam hal ini, hanya DateTimemem-parsing seluruh string dengan benar.
michau
1

Selain jawaban Niels Ganser, Anda dapat mempertimbangkan argumen ini:

Perhatikan bahwa The Ruby Style Guide dengan jelas menyatakan posisi tentang hal ini:

Tanpa DateTime

Jangan gunakan DateTime kecuali Anda perlu memperhitungkan reformasi kalender historis - dan jika Anda melakukannya, tentukan secara eksplisit argumen awal untuk secara jelas menyatakan niat Anda.

# 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)
Paul van Leeuwen
sumber