Differenza tra DateTime e Time in Ruby


218

Qual è la differenza tra DateTimee le Timeclassi in Ruby e quali fattori mi indurrebbe a scegliere l'una o l'altra?


La documentazione ha una sezione che spiega quando usare quale.
x-yuri,

Risposte:


177

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_tvalore 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.


6
Quindi dovrei sempre usare DateTime?
Tom Lehman,

4
Se stai lavorando con le date, direi usare DateTime. Il tempo è conveniente per rappresentare cose come l'ora corrente del giorno o punti nel prossimo futuro come 10.minutes.from_now. I due hanno molto in comune, anche se, come notato, DateTime può rappresentare un intervallo di valori molto più ampio.
tadman,

3
Credo che sia perché Ruby passa a Bignum di lunghezza arbitraria da Fixnum a 32 bit quando si verifica un overflow. I numeri al di fuori di tale intervallo potrebbero non essere supportati da applicazioni esterne. Sì, nel 2038 siamo praticamente fregati fino a quando non saremo tutti d'accordo su un formato orario a 64 bit adeguato. La giuria è ancora fuori.
Tadman,

28
Questa risposta è precedente alla 1.9.2. Ignora tutto ciò che dice sulle limitazioni posix di Time e fai la tua scelta in base alle API di Time e DateTime.
Ben Nagy,

8
Questa risposta è obsoleta, giusto? Ora si consiglia di utilizzare ActiveSupport :: WithTimeZone di Time o Rail. Non è più necessario utilizzare DateTime. È lì per compatibilità con le versioni precedenti.
Donato,

102

[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 Timenon 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 Timequando si tratta esclusivamente di date / orari vicini, passati o futuri e utilizzare solo DateTimequando, 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, Timeora è 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 Integersi usano Bignums ( rispetto a s (valori al di fuori Integerdell'intervallo) o Rationals (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, DateTimenon copre più una gamma più ampia di valori potenziali rispetto aTime .

Inoltre, si DateTimedovrebbero 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, DateTimenon 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, DateTimeha 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 DateTimesé (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.


È ancora vero nel 2018?
Qqwy

2
È ancora vero in Ruby 2.5.1. ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html : "DateTime non considera i secondi bisestili, non tiene traccia delle regole dell'ora legale ". Tuttavia, si noti che DateTime presenta dei vantaggi quando è necessario gestire date / orari del calendario pre-gregoriano. Questo non è stato menzionato qui prima, ma è documentato nella documentazione di riferimento: "[…] La classe Time di Ruby implementa un calendario gregoriano prolettico e non ha alcun concetto di riforma del calendario [...]." Modificherò la mia risposta per fornire ulteriori dettagli.
Niels Ganser,

Timenon 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.
Michau,

@michau Il fatto che Timesupporti 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 +0000while TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-07-01 00:00:00 +0000.
Niels Ganser,

@NielsGanser Fantastico, grazie! Ho suggerito una modifica per chiarire questo punto.
Michau,

44

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

8
Inoltre, Time.new e DateTime.new gestiscono il fuso orario in modo diverso. Sono su GMT + 7, quindi Time.new(2011, 11, 1, 10, 30)produce 2011-11-01 10:30:00 +0700mentre DateTime.new(2011, 11, 1, 10, 30)produce Tue, 01 Nov 2011 10:30:00 +0000.
Phương Nguyễn,

21
E come tutti sappiamo, lo sviluppo attentamente pianificato di Java ha prodotto API logiche esclusivamente semplici.
pje

@ PhươngNguyễn: La prego di aggiungerlo come risposta in modo da poterlo votare? Questo è precisamente il motivo per cui ho deciso di scegliere Time over DateTime.
Senso

@Senseful Mi dispiace non ho ricevuto il tuo messaggio fino ad ora. Le persone hanno già dato alcuni voti al mio commento, quindi penso che sia bello qui.
Phương Nguyễn,

@ PhươngNguyễn Ciao, qualche idea su come / perché questa differenza nei fusi orari? da dove sta prendendo l'offset?
Joel_Blum,

10

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.parsenon è 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.


1
Usando questo in produzione ora, ci sono degli svantaggi nella tecnica?
Alex Moore-Niemi,

1
DateTime non tiene conto dell'ora legale. Pertanto, risparmiando il cambio di tempo, non funzionerà correttamente. DateTime.parse('2014-03-30 01:00:00 +0100').end_of_dayproduce Sun, 30 Mar 2014 23:59:59 +0100, ma Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_dayproduce 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).
Petr '' Bubák '' Šedivý

1
Potrebbe essere ovvio, ma 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.
prusswan,

1
@ Petr''Bubák''Šedivý DateTimeha analizzato l'offset che gli hai dato, che era +0100. Non hai dato Timeun 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.
Mecki,

4

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 accadere solo con il rubino semplice (cioè senza Rails)
vemv,

1

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"


Questa è un'osservazione interessante, ma dovrebbe essere spiegato perché succede. Date(non sorprende) analizza solo le date e quando una Dateviene convertita in Time, utilizza sempre la mezzanotte nel fuso orario locale come ora. La differenza tra Timee TimeDatederiva dal fatto che Timenon 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, DateTimeanalizza correttamente l'intera stringa.
Michau,

1

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.