Diferencia entre DateTime y Time en Ruby

218

¿Cuál es la diferencia entre DateTimey las Timeclases en Ruby y qué factores me harían elegir uno u otro?

Tom Lehman
fuente
Los documentos tienen una sección que explica cuándo usar qué.
x-yuri

Respuestas:

177

Las versiones más recientes de Ruby (2.0+) realmente no tienen diferencias significativas entre las dos clases. Algunas bibliotecas usarán una u otra por razones históricas, pero el nuevo código no necesariamente debe preocuparse. Elegir uno por coherencia es probablemente el mejor, así que intente combinar con lo que esperan sus bibliotecas. Por ejemplo, ActiveRecord prefiere DateTime.

En versiones anteriores a Ruby 1.9 y en muchos sistemas, el tiempo se representa como un valor con signo de 32 bits que describe el número de segundos desde el 1 de enero de 1970 UTC, una envoltura delgada alrededor de un time_tvalor estándar POSIX , y está limitado:

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

Las versiones más nuevas de Ruby pueden manejar valores más grandes sin producir errores.

DateTime es un enfoque basado en el calendario donde el año, mes, día, hora, minuto y segundo se almacenan individualmente. Esta es una construcción de Ruby on Rails que sirve como envoltorio alrededor de los campos DATETIME estándar de SQL. Estos contienen fechas arbitrarias y pueden representar casi cualquier punto en el tiempo ya que el rango de expresión es típicamente muy grande.

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

Por lo tanto, es tranquilizador que DateTime pueda manejar publicaciones de blog de Aristóteles.

Al elegir uno, las diferencias son algo subjetivas ahora. Históricamente, DateTime ha proporcionado mejores opciones para manipularlo en forma de calendario, pero muchos de estos métodos también se han transferido a Time, al menos dentro del entorno Rails.

tadman
fuente
66
Entonces, ¿debería usar siempre DateTime?
Tom Lehman el
44
Si está trabajando con fechas, diría que use DateTime. El tiempo es conveniente para representar cosas como la hora actual del día o puntos en el futuro cercano, como 10.minutes.from_now. Los dos tienen mucho en común, aunque, como se señaló, DateTime puede representar un rango mucho más amplio de valores.
tadman
3
Creo que es porque Ruby cambia a Bignum de longitud arbitraria de Fixnum de 32 bits cuando experimenta un desbordamiento. Los números fuera de ese rango pueden no ser compatibles con aplicaciones externas. Sí, en 2038 estamos básicamente jodidos hasta que todos podamos acordar un formato de tiempo de 64 bits adecuado. El jurado aún está fuera.
tadman
28
Esta respuesta es anterior a 1.9.2. Ignore todo lo que dice sobre las limitaciones posix de Time, y haga su elección en función de las API de Time y DateTime.
Ben Nagy
8
Esta respuesta está desactualizada ¿verdad? Ahora se recomienda utilizar ActiveSupport de Time or Rail :: WithTimeZone. Ya no necesitas usar DateTime. Está ahí para la compatibilidad con versiones anteriores.
Donato
102

[Editar julio de 2018]

Todo lo siguiente sigue siendo válido en Ruby 2.5.1. De la documentación de referencia :

DateTime no considera ningún segundo bisiesto, no rastrea ninguna regla de horario de verano.

Lo que no se ha observado en este hilo antes es una de las pocas ventajas de DateTime: es consciente de las reformas de calendario, mientras Timeque no es:

[...] La clase Ruby's Time implementa un calendario gregoriano proleptico y no tiene un concepto de reforma de calendario [...].

La documentación de referencia concluye con la recomendación de usar Timecuando se trata exclusivamente de fechas / horas pasadas, actuales o futuras y solo DateTimecuando, por ejemplo, el cumpleaños de Shakespeare debe convertirse con precisión: (énfasis agregado)

Entonces, ¿cuándo debe usar DateTime en Ruby y cuándo debe usar Time? Es casi seguro que querrá usar Time ya que su aplicación probablemente esté tratando con fechas y horas actuales. Sin embargo, si necesita lidiar con fechas y horas en un contexto histórico, querrá usar DateTime [...]. Si también tiene que lidiar con zonas horarias, la mejor de las suertes: tenga en cuenta que probablemente estará lidiando con los horarios solares locales, ya que no fue hasta el siglo XIX que la introducción de los ferrocarriles exigió la necesidad de un horario estándar. y eventualmente zonas horarias.

[/ Editar julio 2018]

A partir de ruby ​​2.0, la mayoría de la información en las otras respuestas está desactualizada.

En particular, Timeahora está prácticamente sin consolidar. Puede estar a más o menos de 63 bits de distancia de 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

La única consecuencia del uso de valores más grandes debería ser el rendimiento, que es mejor cuando Integerse usan Bignums ( frente a s (valores fuera del Integerrango) o Rationals (cuando se rastrean nanosegundos)):

Desde Ruby 1.9.2, la implementación de Time utiliza un entero de 63 bits con signo, Bignum o Rational. El número entero es un número de nanosegundos desde la época que puede representar 1823-11-12 a 2116-02-20. Cuando se usa Bignum o Rational (antes de 1823, después de 2116, en nanosegundos), el tiempo funciona más lento como cuando se usa un entero. ( http://www.ruby-doc.org/core-2.1.0/Time.html )

En otras palabras, hasta donde yo entiendo, DateTimeya no cubre un rango más amplio de valores potenciales queTime .

Además, DateTimeprobablemente deberían tenerse en cuenta dos restricciones no mencionadas anteriormente :

DateTime no considera ningún salto de segundos, no rastrea ninguna regla de horario de verano. ( http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime )

Primero, DateTimeno tiene concepto de segundos intercalares:

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

Para que funcione el ejemplo anterior Time, el sistema operativo debe admitir segundos bisiestos y la información de la zona horaria debe configurarse correctamente, por ejemplo, a través deTZ=right/UTC irb (en muchos sistemas Unix).

En segundo lugar, DateTimetiene una comprensión muy limitada de las zonas horarias y, en particular, no tiene un concepto de horario de verano . Prácticamente maneja zonas horarias como simples compensaciones 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>

Esto puede causar problemas cuando las horas se ingresan como DST y luego se convierten en una zona horaria que no es DST sin realizar un seguimiento de las compensaciones correctas fuera de DateTime sí mismo (muchos sistemas operativos ya pueden ocuparse de esto por usted).

En general, diría que hoy en día Timees la mejor opción para la mayoría de las aplicaciones.

También tenga en cuenta una diferencia importante en la suma: cuando agrega un número a un objeto Time, se cuenta en segundos, pero cuando agrega un número a DateTime, se cuenta en días.

Niels Ganser
fuente
¿Sigue siendo cierto en 2018?
Qqwy
2
Todavía es cierto en Ruby 2.5.1. ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/DateTime.html : "DateTime no considera ningún segundo bisiesto, no rastrea ninguna regla de horario de verano". Sin embargo, tenga en cuenta que DateTime tiene ventajas cuando necesita lidiar con fechas / horas de calendario pre-gregorianas. Esto no se mencionó aquí antes, pero está documentado en la documentación de referencia: "[...] La clase Ruby's Time implementa un calendario gregoriano proleptico y no tiene un concepto de reforma de calendario [...]". Editaré mi respuesta para proporcionar más detalles.
Niels Ganser
Timetampoco tiene un concepto de segundos intercalares, por lo que no es diferente de DateTime. No sé dónde ejecutó sus ejemplos, pero lo intenté Time.new(2012,6,30,23,59,60,0)en diferentes versiones de Ruby, de 2.0 a 2.7, y siempre lo obtuve 2012-07-01 00:00:00 +0000.
michau
@michau Si Timeadmite segundos intercalares o no, depende de la configuración de su sistema operativo y zona horaria. Por ejemplo: TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-06-30 23:59:60 +0000mientras que TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'=> 2012-07-01 00:00:00 +0000.
Niels Ganser
@NielsGanser Genial, gracias! Sugerí una edición para aclarar este punto.
michau
44

Creo que la respuesta a "cuál es la diferencia" es una de las desafortunadas respuestas comunes a esta pregunta en las bibliotecas estándar de Ruby: las dos clases / libs fueron creadas de manera diferente por diferentes personas en diferentes momentos. Es una de las desafortunadas consecuencias de la naturaleza comunitaria de la evolución de Ruby en comparación con el desarrollo cuidadosamente planificado de algo como Java. Los desarrolladores quieren una nueva funcionalidad pero no quieren pisar las API existentes, por lo que solo crean una nueva clase; para el usuario final no hay una razón obvia para que existan las dos.

Esto es cierto para las bibliotecas de software en general: a menudo la razón por la que algún código o API es la forma en que resulta ser histórica en lugar de lógica.

La tentación es comenzar con DateTime porque parece más genérico. Fecha ... y hora, ¿verdad? Incorrecto. El tiempo también mejora las fechas, y de hecho puede analizar zonas horarias donde DateTime no puede. También se desempeña mejor.

Terminé usando el tiempo en todas partes.

Sin embargo, para estar seguro, tiendo a permitir que los argumentos de DateTime se pasen a mis API Timey, y convertirlos. Además, si sé que ambos tienen el método que me interesa, acepto cualquiera de ellos, como este método que escribí para convertir tiempos a XML (para archivos 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
Ruibarbo
fuente
8
Además, Time.new y DateTime.new tratan la zona horaria de manera diferente. Estoy en GMT + 7, así que Time.new(2011, 11, 1, 10, 30)produce 2011-11-01 10:30:00 +0700mientras DateTime.new(2011, 11, 1, 10, 30)produce Tue, 01 Nov 2011 10:30:00 +0000.
Phương Nguyễn
21
Y como todos sabemos, el desarrollo cuidadosamente planificado de Java resultó en API lógicas y exclusivamente simples.
pje
@ PhươngNguyễn: ¿Podrían agregar eso como respuesta para que pueda votar? Esa es precisamente la razón por la que decidí elegir Time en DateTime.
Senseful
@Senseful Lo siento, no he recibido tu mensaje hasta ahora. La gente ya da algunos votos a mi comentario, así que creo que es genial aquí.
Phương Nguyễn
@ PhươngNguyễn Hola, ¿alguna idea de cómo / por qué esta diferencia en las zonas horarias? ¿De dónde está tomando el desplazamiento?
Joel_Blum
10

Descubrí que cosas tales como analizar y calcular el inicio / final de un día en diferentes zonas horarias son más fáciles de hacer con DateTime, suponiendo que esté utilizando las extensiones ActiveSupport .

En mi caso, necesitaba calcular el final del día en la zona horaria de un usuario (arbitraria) en función de la hora local del usuario que recibí como una cadena, por ejemplo, "2012-10-10 10:10 +0300"

Con DateTime es tan simple como

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

Ahora intentemos de la misma manera 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.

En realidad, Time necesita saber la zona horaria antes de analizar (también tenga en cuenta que Time.zone.parseno 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

Entonces, en este caso, definitivamente es más fácil usar DateTime.

Yuriy Kharchenko
fuente
1
Usando esto en la producción ahora, ¿hay algún inconveniente en la técnica?
Alex Moore-Niemi
1
DateTime no tiene en cuenta el horario de verano. Por lo tanto, al ahorrar tiempo de cambio, no funcionará correctamente. DateTime.parse('2014-03-30 01:00:00 +0100').end_of_dayproduce Sun, 30 Mar 2014 23:59:59 +0100, pero 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 - observe que el desplazamiento ha cambiado). Pero para hacer esto, necesita más información sobre la zona horaria del usuario (no solo la compensación sino también si se usa el ahorro de tiempo).
Petr '' Bubák '' Šedivý
1
Puede indicar lo obvio, pero Time.zone.parsees muy útil al analizar los tiempos con diferentes zonas: te obliga a pensar qué zona deberías usar. A veces, Time.find_zone funciona aún mejor.
prusswan
1
@ Petr''Bubák''Šedivý analizó DateTimeel desplazamiento que le diste, que fue +0100. No proporcionó Timeun desplazamiento, pero una zona horaria ("CET" no describe un desplazamiento, es el nombre de una zona horaria). Las zonas horarias pueden tener diferentes compensaciones durante todo el año, pero una compensación es una compensación y siempre es la misma.
Mecki
4

Considere cómo manejan las zonas horarias de manera diferente con instancias personalizadas:

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

Esto puede ser complicado al crear rangos de tiempo, etc.

gr8scott06
fuente
Solo parece suceder con rubí simple (es decir, sin rieles)
vemv
1

Parece que en algunos casos el comportamiento es muy diferente:

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"

Alexey Strizhak
fuente
Esa es una observación interesante, pero debería explicarse por qué sucede. Date(como era de esperar) analiza solo las fechas, y cuando Datese convierte a Time, siempre usa la medianoche en la zona horaria local como la hora. La diferencia entre Timey se TimeDatederiva del hecho de que Timeno comprende BST, lo cual es sorprendente, dado que las zonas horarias generalmente se manejan más correctamente Time(con respecto al horario de verano, por ejemplo). Entonces, en este caso, solo DateTimeanaliza la cadena completa correctamente.
michau
1

Además de la respuesta de Niels Ganser , podría considerar este argumento:

Tenga en cuenta que la Guía de estilo Ruby establece claramente una posición al respecto:

Sin fecha y hora

No use DateTime a menos que necesite tener en cuenta la reforma del calendario histórico, y si lo hace, especifique explícitamente el argumento de inicio para establecer claramente sus intenciones.

# 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
fuente