Qual è la differenza tra DateTime
e le Time
classi in Ruby e quali fattori mi indurrebbe a scegliere l'una o l'altra?
Qual è la differenza tra DateTime
e le Time
classi in Ruby e quali fattori mi indurrebbe a scegliere l'una o l'altra?
Risposte:
Le versioni più recenti di Ruby (2.0+) non presentano differenze significative tra le due classi. Alcune biblioteche useranno l'una o l'altra per ragioni storiche, ma non è necessario preoccuparsi del nuovo codice. Scegline uno per coerenza è probabilmente il migliore, quindi cerca di adattarti alle aspettative delle tue librerie. Ad esempio, ActiveRecord preferisce DateTime.
Nelle versioni precedenti a Ruby 1.9 e su molti sistemi, Time è rappresentato come un valore con segno a 32 bit che descrive il numero di secondi dal 1 ° gennaio 1970 UTC, un wrapper sottile attorno a un time_t
valore standard POSIX , ed è limitato:
Time.at(0x7FFFFFFF)
# => Mon Jan 18 22:14:07 -0500 2038
Time.at(-0x7FFFFFFF)
# => Fri Dec 13 15:45:53 -0500 1901
Le versioni più recenti di Ruby sono in grado di gestire valori più grandi senza produrre errori.
DateTime è un approccio basato su calendario in cui anno, mese, giorno, ora, minuti e secondi vengono memorizzati singolarmente. Questo è un costrutto Ruby on Rails che funge da wrapper per i campi DATETIME standard SQL. Questi contengono date arbitrarie e possono rappresentare quasi ogni punto nel tempo poiché l'intervallo di espressione è in genere molto ampio.
DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000
Quindi è rassicurante che DateTime possa gestire i post sul blog di Aristotele.
Quando si sceglie uno, le differenze sono in qualche modo soggettive ora. Storicamente DateTime ha fornito opzioni migliori per manipolarlo in modo calendario, ma molti di questi metodi sono stati trasferiti anche su Time, almeno all'interno dell'ambiente Rails.
[Modifica luglio 2018]
Tutto quanto sotto è ancora valido in Ruby 2.5.1. Dalla documentazione di riferimento :
DateTime non considera alcun secondo bisestile, non tiene traccia delle regole dell'ora legale.
Ciò che non è stato notato prima in questo thread è uno dei pochi vantaggi di DateTime
: è a conoscenza delle riforme del calendario mentre Time
non lo è:
[...] La classe Time di Ruby implementa un proletico calendario gregoriano e non ha alcun concetto di riforma del calendario [...].
La documentazione di riferimento si conclude con la raccomandazione di utilizzare Time
quando si tratta esclusivamente di date / orari vicini, passati o futuri e utilizzare solo DateTime
quando, ad esempio, il compleanno di Shakespeare deve essere accuratamente convertito: (enfasi aggiunta)
Quindi, quando dovresti usare DateTime in Ruby e quando dovresti usare Time? Quasi sicuramente vorrai usare Time poiché la tua app probabilmente sta gestendo date e orari correnti. Tuttavia, se devi gestire date e orari in un contesto storico, ti consigliamo di utilizzare DateTime […]. Se anche tu hai a che fare con i fusi orari, allora buona fortuna - tieni presente che probabilmente avrai a che fare con i tempi solari locali, dal momento che non è stato fino al 19 ° secolo che l'introduzione delle ferrovie ha reso necessaria la necessità di tempo standard e infine fusi orari.
[/ Modifica luglio 2018]
A partire da ruby 2.0, la maggior parte delle informazioni nelle altre risposte è obsoleta.
In particolare, Time
ora è praticamente illimitato. Può essere più o meno anche di 63 bit dall'epoca:
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
L'unica conseguenza dell'utilizzo di valori più grandi dovrebbe essere la prestazione, che è migliore quando Integer
si usano Bignum
s ( rispetto a s (valori al di fuori Integer
dell'intervallo) o Rational
s (quando vengono tracciati i nanosecondi)):
A partire da Ruby 1.9.2, l'implementazione Time utilizza un intero con segno a 63 bit, Bignum o Rational. L'intero è un numero di nanosecondi dall'Epoca che può rappresentare dal 1823-11-12 al 2116-02-20. Quando si usa Bignum o Rational (prima del 1823, dopo il 2116, sotto i nanosecondi), il Tempo funziona più lentamente come quando si usa un numero intero. ( http://www.ruby-doc.org/core-2.1.0/Time.html )
In altre parole, per quanto ho capito, DateTime
non copre più una gamma più ampia di valori potenziali rispetto aTime
.
Inoltre, si DateTime
dovrebbero probabilmente notare due restrizioni precedentemente non menzionate :
DateTime non prende in considerazione alcun periodo di secondo, non tiene traccia delle regole dell'ora legale. ( http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime )
Innanzitutto, DateTime
non ha alcun concetto di secondi bisestili:
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
Affinché l'esempio di cui sopra funzioni Time
, il sistema operativo deve supportare i secondi intercalari e le informazioni sul fuso orario devono essere impostate correttamente, ad esempio attraverso TZ=right/UTC irb
(su molti sistemi Unix).
In secondo luogo, DateTime
ha una comprensione molto limitata dei fusi orari e in particolare non ha il concetto di ora legale . Gestisce praticamente i fusi orari come semplici 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>
Ciò può causare problemi quando i tempi vengono immessi come ora legale e quindi convertiti in un fuso orario non DST senza tenere traccia degli offset corretti al di fuori di DateTime
sé (molti sistemi operativi potrebbero effettivamente occuparsene già).
Nel complesso, direi che al giorno d'oggi Time
è la scelta migliore per la maggior parte delle applicazioni.
Nota anche una differenza importante in aggiunta: quando aggiungi un numero a un oggetto Time, viene contato in secondi, ma quando aggiungi un numero a un DateTime, viene conteggiato in giorni.
Time
non ha neanche il concetto di secondi saltati, quindi non è diverso da DateTime
. Non so dove hai eseguito i tuoi esempi, ma ho provato Time.new(2012,6,30,23,59,60,0)
in diverse versioni di Ruby, dalla 2.0 alla 2.7, e ho sempre ottenuto 2012-07-01 00:00:00 +0000
.
Time
supporti i secondi bisestili o meno dipende dal sistema operativo e dalla configurazione del fuso orario. Ad esempio: TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-06-30 23:59:60 +0000
while TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-07-01 00:00:00 +0000
.
Penso che la risposta a "qual è la differenza" sia una delle sfortunate risposte comuni a questa domanda nelle librerie standard di Ruby: le due classi / librerie sono state create in modo diverso da persone diverse in momenti diversi. È una delle sfortunate conseguenze della natura comunitaria dell'evoluzione di Ruby rispetto allo sviluppo attentamente pianificato di qualcosa come Java. Gli sviluppatori vogliono nuove funzionalità ma non vogliono fare un passo sulle API esistenti, quindi creano semplicemente una nuova classe: per l'utente finale non c'è motivo ovvio che i due esistano.
Questo è vero per le librerie di software in generale: spesso la ragione per cui un po 'di codice o API è il modo in cui risulta essere storica piuttosto che logica.
La tentazione è di iniziare con DateTime perché sembra più generico. Data ... e ora, giusto? Sbagliato. Anche il tempo fa le date meglio, e infatti può analizzare i fusi orari dove DateTime non può. Inoltre si comporta meglio.
Ho finito per usare Time ovunque.
Per sicurezza, tuttavia, tendo a consentire che gli argomenti DateTime vengano passati alle mie API Timey e che vengano convertiti. Anche se so che entrambi hanno il metodo che mi interessa, accetto uno dei due, come questo metodo che ho scritto per convertire i tempi in XML (per i 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
Time.new(2011, 11, 1, 10, 30)
produce 2011-11-01 10:30:00 +0700
mentre DateTime.new(2011, 11, 1, 10, 30)
produce Tue, 01 Nov 2011 10:30:00 +0000
.
Ho trovato cose come l'analisi e il calcolo dell'inizio / fine di un giorno in diversi fusi orari sono più facili da fare con DateTime, supponendo che tu stia utilizzando le estensioni ActiveSupport .
Nel mio caso, dovevo calcolare la fine della giornata nel fuso orario di un utente (arbitrario) in base all'ora locale dell'utente che ho ricevuto come stringa, ad esempio "2012-10-10 10:10 +0300"
Con DateTime è semplice come
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
Ora proviamo allo stesso modo con 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.
In realtà, Time deve conoscere il fuso orario prima di analizzare (nota anche che Time.zone.parse
non è 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
Quindi, in questo caso, è sicuramente più facile andare con DateTime.
DateTime.parse('2014-03-30 01:00:00 +0100').end_of_day
produce Sun, 30 Mar 2014 23:59:59 +0100
, ma Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_day
produce Sun, 30 Mar 2014 23:59:59 CEST +02:00
(CET = + 01: 00, CEST = + 02: 00 - notare che l'offset è cambiato). Ma per fare questo, hai bisogno di maggiori informazioni sul fuso orario dell'utente (non solo offset, ma anche se viene utilizzato il risparmio di tempo).
Time.zone.parse
è molto utile quando si analizzano i tempi con diverse zone - ti costringe a pensare a quale zona dovresti usare. A volte, Time.find_zone funziona ancora meglio.
DateTime
ha analizzato l'offset che gli hai dato, che era +0100. Non hai dato Time
un offset, ma un fuso orario ("CET" non descrive un offset, è il nome di un fuso orario). I fusi orari possono avere offset diversi durante l'anno, ma un offset è un offset ed è sempre lo stesso.
Considera come gestiscono i fusi orari in modo diverso con le istanze personalizzate:
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
Questo può essere difficile quando si creano intervalli di tempo, ecc.
Sembra che in alcuni casi il comportamento sia molto diverso:
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
"27/06/2018 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
(non sorprende) analizza solo le date e quando una Date
viene convertita in Time
, utilizza sempre la mezzanotte nel fuso orario locale come ora. La differenza tra Time
e TimeDate
deriva dal fatto che Time
non comprende il BST, il che è sorprendente, dato che i fusi orari vengono generalmente gestiti in modo più corretto Time
(per quanto riguarda l'ora legale, ad esempio). Quindi, in questo caso, DateTime
analizza correttamente l'intera stringa.
Oltre alla risposta di Niels Ganser potresti considerare questo argomento:
Nota che la Ruby Style Guide afferma chiaramente una posizione al riguardo:
No DateTime
Non utilizzare DateTime a meno che non sia necessario tenere conto della riforma del calendario storico e, in tal caso, specificare esplicitamente l'argomento iniziale per indicare chiaramente le proprie intenzioni.
# 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)