Convierte a / desde DateTime y Time en Ruby

132

¿Cómo se convierte entre un objeto DateTime y Time en Ruby?

Solo lectura
fuente
1
No estoy seguro de si esta debería ser una pregunta separada, pero ¿cómo se convierte entre una fecha y una hora?
Andrew Grimm
8
Las respuestas aceptadas y mejor calificadas ya no son las más precisas en las versiones modernas de Ruby. Vea las respuestas de @theTinMan y @PatrickMcKenzie a continuación.
Phrogz

Respuestas:

50

Necesitarás dos conversiones ligeramente diferentes.

Para convertir de Time a DateTimepuede modificar la clase Time de la siguiente manera:

require 'date'
class Time
  def to_datetime
    # Convert seconds + microseconds into a fractional number of seconds
    seconds = sec + Rational(usec, 10**6)

    # Convert a UTC offset measured in minutes to one measured in a
    # fraction of a day.
    offset = Rational(utc_offset, 60 * 60 * 24)
    DateTime.new(year, month, day, hour, min, seconds, offset)
  end
end

Ajustes similares a Fecha le permitirán convertir DateTime a Time .

class Date
  def to_gm_time
    to_time(new_offset, :gm)
  end

  def to_local_time
    to_time(new_offset(DateTime.now.offset-offset), :local)
  end

  private
  def to_time(dest, method)
    #Convert a fraction of a day to a number of microseconds
    usec = (dest.sec_fraction * 60 * 60 * 24 * (10**6)).to_i
    Time.send(method, dest.year, dest.month, dest.day, dest.hour, dest.min,
              dest.sec, usec)
  end
end

Tenga en cuenta que debe elegir entre la hora local y la hora GM / UTC.

Ambos fragmentos de código anteriores están tomados del libro de cocina Ruby de O'Reilly . Su política de reutilización de código lo permite.

Gordon Wilson
fuente
55
Esto se interrumpirá en 1.9 donde DateTime # sec_fraction devuelve el número de milisegundos en un segundo. Para 1.9 que desea utilizar: usec = dest.sec_fraction * 10 ** 6
dkubb
185
require 'time'
require 'date'

t = Time.now
d = DateTime.now

dd = DateTime.parse(t.to_s)
tt = Time.parse(d.to_s)
anshul
fuente
13
+1 Puede que no sea la ejecución más eficiente, pero funciona, es conciso y es muy legible.
Walt Jones
66
Desafortunadamente, esto solo funciona cuando se trata de horarios locales. Si comienza con un DateTime u Time con una zona horaria diferente, la función de análisis se convertirá en zona horaria local. Básicamente pierdes la zona horaria original.
Bernard
66
A partir de ruby ​​1.9.1, DateTime.parse conserva la zona horaria. (No tengo acceso a versiones anteriores). Time.parse no conserva la zona horaria, porque representa el tiempo_t estándar POSIX, que creo que es una diferencia entera de la época. Cualquier conversión al tiempo debe tener el mismo comportamiento.
anshul
1
Tienes razón. DateTime.parse funciona en 1.9.1 pero no Time.parse. En cualquier caso, es menos propenso a errores (consistente) y probablemente más rápido usar DateTime.new (...) y Time.new (..). Vea mi respuesta para el código de muestra.
Bernard
1
Hola @anshul No estoy implicando que estoy diciendo :-). La información de zona horaria no se mantiene cuando se usa Time.parse (). Es fácil de probar. En su código anterior, simplemente reemplace d = DateTime.now con d = DateTime.new (2010,01,01, 10,00,00, Rational (-2, 24)). Ahora mostrará la fecha d convertida a su zona horaria local. Todavía puede hacer aritmética de fechas y todo, pero se pierde la información original de tz. Esta información es un contexto para la fecha y a menudo es importante. Ver aquí: stackoverflow.com/questions/279769/...
Bernard
63

Como una actualización del estado del ecosistema de Ruby Date, DateTimey Timeahora tienen métodos para convertir entre las diferentes clases. Usando Ruby 1.9.2+:

pry
[1] pry(main)> ts = 'Jan 1, 2000 12:01:01'
=> "Jan 1, 2000 12:01:01"
[2] pry(main)> require 'time'
=> true
[3] pry(main)> require 'date'
=> true
[4] pry(main)> ds = Date.parse(ts)
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[5] pry(main)> ds.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[6] pry(main)> ds.to_datetime
=> #<DateTime: 2000-01-01T00:00:00+00:00 (4903089/2,0,2299161)>
[7] pry(main)> ds.to_time
=> 2000-01-01 00:00:00 -0700
[8] pry(main)> ds.to_time.class
=> Time
[9] pry(main)> ds.to_datetime.class
=> DateTime
[10] pry(main)> ts = Time.parse(ts)
=> 2000-01-01 12:01:01 -0700
[11] pry(main)> ts.class
=> Time
[12] pry(main)> ts.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[13] pry(main)> ts.to_date.class
=> Date
[14] pry(main)> ts.to_datetime
=> #<DateTime: 2000-01-01T12:01:01-07:00 (211813513261/86400,-7/24,2299161)>
[15] pry(main)> ts.to_datetime.class
=> DateTime
el hombre de hojalata
fuente
1
DateTime.to_time devuelve un DateTime ... 1.9.3p327 :007 > ts = '2000-01-01 12:01:01 -0700' => "2000-01-01 12:01:01 -0700" 1.9.3p327 :009 > dt = ts.to_datetime => Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :010 > dt.to_time => Sat, 01 Jan 2000 12:01:01 -0700 1.9.3p327 :011 > dt.to_time.class => DateTime
Jesse Clark
Ups Me acabo de dar cuenta de que este es un problema de Ruby on Rails, no un problema de Ruby: stackoverflow.com/questions/11277454/… . Incluso tenían un error archivado contra este método en la línea 2.x y lo marcaron "no solucionará". Decisión horrible en mi humilde opinión. El comportamiento de Rails rompe totalmente la interfaz subyacente de Ruby.
Jesse Clark
12

Desafortunadamente, las funciones DateTime.to_time, Time.to_datetimey Time.parseno retienen la información de la zona horaria. Todo se convierte a la zona horaria local durante la conversión. La aritmética de fechas todavía funciona, pero no podrá mostrar las fechas con sus zonas horarias originales. Esa información de contexto es a menudo importante. Por ejemplo, si quiero ver las transacciones realizadas durante el horario comercial en Nueva York, probablemente prefiera verlas en sus zonas horarias originales, no en mi zona horaria local en Australia (12 horas antes de Nueva York).

Los siguientes métodos de conversión mantienen esa información tz.

Para Ruby 1.8, mira la respuesta de Gordon Wilson . Es del viejo y confiable Ruby Cookbook.

Para Ruby 1.9, es un poco más fácil.

require 'date'

# Create a date in some foreign time zone (middle of the Atlantic)
d = DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24))
puts d

# Convert DateTime to Time, keeping the original timezone
t = Time.new(d.year, d.month, d.day, d.hour, d.min, d.sec, d.zone)
puts t

# Convert Time to DateTime, keeping the original timezone
d = DateTime.new(t.year, t.month, t.day, t.hour, t.min, t.sec, Rational(t.gmt_offset / 3600, 24))
puts d

Esto imprime lo siguiente

2010-01-01T10:00:00-02:00
2010-01-01 10:00:00 -0200
2010-01-01T10:00:00-02:00

Se conserva toda la información original de DateTime, incluida la zona horaria.

Bernardo
fuente
2
El tiempo es complicado, pero no hay excusa para no proporcionar una conversión integrada entre diferentes clases de tiempo incorporado. Puede lanzar una RangeException si intenta obtener un UNIX time_t para 4713 BC (aunque un valor negativo BigNum sería mejor), pero al menos proporcionar un método para ello.
Mark Reed
1
Time#to_datetimeparece preservar tz para mí:Time.local(0).to_datetime.zone #=> "-07:00"; Time.gm(0).to_datetime.zone #=> "+00:00"
Phrogz
El desplazamiento de @Phrogz UTC no es lo mismo que una zona horaria. Uno es constante, el otro puede cambiar en diferentes momentos del año para el horario de verano. DateTime no tiene una zona, ignora el horario de verano. El tiempo lo respeta, pero solo en el TZ "local" (entorno del sistema).
Andrew Vit
1

Mejorando la solución de Gordon Wilson, aquí está mi intento:

def to_time
  #Convert a fraction of a day to a number of microseconds
  usec = (sec_fraction * 60 * 60 * 24 * (10**6)).to_i
  t = Time.gm(year, month, day, hour, min, sec, usec)
  t - offset.abs.div(SECONDS_IN_DAY)
end

Obtendrá la misma hora en UTC, perdiendo la zona horaria (desafortunadamente)

Además, si tienes ruby ​​1.9, prueba el to_timemétodo

Mildred
fuente
0

Al realizar tales conversiones, uno debe tener en cuenta el comportamiento de las zonas horarias al convertir de un objeto a otro. Encontré algunas buenas notas y ejemplos en esta publicación de stackoverflow .

gato
fuente