Diferencia entre DateTime y Time en Ruby
time ruby (7)
¿Cuál es la diferencia entre las clases DateTime
y Time
en Ruby y qué factores me harían elegir una u otra?
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.
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 consecuencias desafortunadas de la naturaleza comunitaria de la evolución de Ruby en comparación con el desarrollo cuidadosamente planeado de algo como Java. Los desarrolladores desean una nueva funcionalidad, pero no quieren pisar las API existentes, por lo que simplemente crean una nueva clase: para el usuario final no hay ninguna 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 un código o API es la forma en que resulta ser más histórica que lógica.
La tentación es comenzar con DateTime porque parece más genérico. Fecha ... y hora, ¿verdad? Incorrecto. El tiempo también hace las fechas mejor, y de hecho puede analizar las zonas horarias donde DateTime no puede. También se desempeña mejor.
He terminado usando el tiempo en todas partes.
Sin embargo, para estar seguro, tiendo a permitir que los argumentos de DateTime se pasen a mis API de Timey, y convertirlos. Además, si sé que ambos tienen el método en el que estoy interesado, acepto cualquiera, como este método que escribí para convertir los 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
Encontré cosas tales como analizar y calcular el comienzo / final de un día en diferentes zonas horarias que son más fáciles de hacer con DateTime, asumiendo que estás usando las extensiones ActiveSupport .
En mi caso, tuve que calcular el final del día en la zona horaria de un usuario (arbitraria) según 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 intentémoslo 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 es 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 es definitivamente más fácil ir con DateTime.
Las versiones más nuevas de Ruby (2.0+) no tienen realmente diferencias significativas entre las dos clases. Algunas bibliotecas usarán una u otra por razones históricas, pero el código nuevo no necesariamente tiene que preocuparse. Elegir uno por coherencia es probablemente lo 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, Time 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 valor de time_t
estándar POSIX, y está delimitada:
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 son capaces de 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 suele ser muy grande.
DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000
Así que es tranquilizador que DateTime pueda manejar las publicaciones de blogs de Aristóteles.
Al elegir uno, las diferencias son algo subjetivas ahora. Históricamente, DateTime ha brindado mejores opciones para manipularlo en forma de calendario, pero muchos de estos métodos también se han trasladado a Time, al menos dentro del entorno de Rails.
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
"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"
[Editar Julio 2018]
Todo lo de abajo sigue siendo válido en Ruby 2.5.1. De la documentación de referencia :
DateTime no considera ningún segundo salto, no rastrea ninguna regla de horario de verano.
Lo que no se ha señalado anteriormente en este hilo es una de las pocas ventajas de DateTime
: es consciente de las reformas del calendario, mientras que Time
no lo es:
[…] La clase de 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 usa DateTime
cuando, por ejemplo, el cumpleaños de Shakespeare se debe convertir con precisión: (énfasis agregado)
Entonces, ¿cuándo debería usar DateTime en Ruby y cuándo debería usar Time? Es casi seguro que querrá usar el Tiempo, ya que su aplicación probablemente esté tratando con fechas y horas actuales. Sin embargo, si necesita tratar fechas y horas en un contexto histórico, querrá usar DateTime [...]. Si también tiene que lidiar con las zonas horarias, la mejor de las suertes es que tenga en cuenta que probablemente tendrá que lidiar con los tiempos solares locales, ya que no fue hasta el siglo XIX cuando la introducción de los ferrocarriles requirió la necesidad del Tiempo Estándar. y eventualmente las zonas horarias.
[/ Editar Julio 2018]
A partir de Ruby 2.0, la mayor parte de la información en las otras respuestas está desactualizada.
En particular, el Time
ahora está prácticamente sin consolidar. Puede estar más o menos a 63 bits 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 de usar valores más grandes debería ser el rendimiento, que es mejor cuando se usan Bignum
(frente a Bignum
s (valores fuera del rango de Bignum
Integer
) o Rational
s (cuando se rastrean los 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 que cuando se usa un entero. ( http://www.ruby-doc.org/core-2.1.0/Time.html )
En otras palabras, por lo que entiendo, DateTime
ya no cubre un rango más amplio de valores potenciales que Time
.
Además, probablemente se deben tener en cuenta dos restricciones no mencionadas anteriormente de DateTime
:
DateTime no considera ningún salto de segundo, 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 de salto:
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_i
=> 1341100823
Segundo, DateTime
tiene un conocimiento muy limitado de las zonas horarias y, en particular, no tiene un concepto de ahorro de luz diurna . Maneja prácticamente las 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 los tiempos se ingresan como DST y luego se convierten en una zona horaria no DST sin tener que realizar un seguimiento de las compensaciones correctas fuera de DateTime
(muchos sistemas operativos pueden ya encargarse de esto).
En general, diría que hoy en día el Time
es la mejor opción para la mayoría de las aplicaciones.
También tenga en cuenta una diferencia importante en la adición: cuando agrega un número a un objeto Tiempo, se cuenta en segundos, pero cuando agrega un número a un Fecha y hora, se cuenta en días.
¡Anticuado! Vea abajo...
La diferencia de rendimiento no se puede enfatizar lo suficiente ... El tiempo es C y DateTime es Ruby:
>> Benchmark.bm do |bm|
?> bm.report(''DateTime:'') do
?> n1 = DateTime.now
>> n2 = DateTime.now
>> 1_000_000.times{ n1 < n2 }
>> end
>> bm.report(''Time: '') do
?> n1 = Time.now
>> n2 = Time.now
>> 1_000_000.times{ n1 < n2 }
>> end
>> end
user system total real
DateTime: 4.980000 0.020000 5.000000 ( 5.063963)
Time: 0.330000 0.000000 0.330000 ( 0.335913)
Actualización (2/2012):
Como ya se mencionó en el comentario, 1.9.3 ha mejorado enormemente el rendimiento de DateTime
:
user system total real
DateTime: 0.330000 0.000000 0.330000 ( 0.333869)
Time: 0.300000 0.000000 0.300000 ( 0.306444)