¿Cuál es la diferencia entre DateTime
y las Time
clases en Ruby y qué factores me harían elegir uno u otro?
¿Cuál es la diferencia entre DateTime
y las Time
clases en Ruby y qué factores me harían elegir uno u otro?
Respuestas:
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_t
valor 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.
[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 Time
que 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 Time
cuando se trata exclusivamente de fechas / horas pasadas, actuales o futuras y solo DateTime
cuando, 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, Time
ahora 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 Integer
se usan Bignum
s ( frente a s (valores fuera del Integer
rango) o Rational
s (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, DateTime
ya no cubre un rango más amplio de valores potenciales queTime
.
Además, DateTime
probablemente 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, DateTime
no 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, DateTime
tiene 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 Time
es 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.
Time
tampoco 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
.
Time
admite 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 +0000
mientras que TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-07-01 00:00:00 +0000
.
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
Time.new(2011, 11, 1, 10, 30)
produce 2011-11-01 10:30:00 +0700
mientras DateTime.new(2011, 11, 1, 10, 30)
produce Tue, 01 Nov 2011 10:30:00 +0000
.
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.parse
no 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.
DateTime.parse('2014-03-30 01:00:00 +0100').end_of_day
produce Sun, 30 Mar 2014 23:59:59 +0100
, pero 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 - 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).
Time.zone.parse
es 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.
DateTime
el desplazamiento que le diste, que fue +0100. No proporcionó Time
un 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.
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.
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"
Date
(como era de esperar) analiza solo las fechas, y cuando Date
se convierte a Time
, siempre usa la medianoche en la zona horaria local como la hora. La diferencia entre Time
y se TimeDate
deriva del hecho de que Time
no 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 DateTime
analiza la cadena completa correctamente.
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)