ruby-on-rails - now - ruby time zone
Strptime con Timezone (8)
Tengo una cadena que analizo con DateTime.strptime
. La zona horaria de la fecha en la cadena es CET, pero Ruby crea un objeto UTC DateTime que, por supuesto, tiene un desplazamiento de 2 horas.
Actualmente estoy DateTime.strptime().change(:offset => "+0020")
el problema con DateTime.strptime().change(:offset => "+0020")
pero estoy bastante seguro de que esta no es la forma en que debe funcionar.
¿Puede alguien iluminarme sobre la forma correcta de hacer esto?
Al menos a partir de Rails 4.2 (quizás antes, no lo he probado), siempre que use Time#strptime
lugar de DateTime.strptime
, la zona horaria actual se aplica automáticamente:
> DateTime.strptime(''12/28/2016 5:00 PM'', ''%m/%d/%Y %H:%M %p'')
=> Wed, 28 Dec 2016 17:00:00 +0000
> Time.strptime(''12/28/2016 5:00 PM'', ''%m/%d/%Y %H:%M %p'')
=> 2016-12-28 17:00:00 -0800
Esto funciona en Rails 5:
Time.zone.strptime("2017-05-20 18:20:10", "%Y-%m-%d %H:%M:%S")
Devuelve la hora en la zona horaria especificada.
He estado luchando contra este mismo problema al tratar de analizar una cadena de fecha de un formulario y guardarla independientemente de la hora del servidor. El proceso que he seguido es como tal. Aseguro que las zonas horarias RoR y Ruby están configuradas en UTC. Lo hace más fácil si el servidor también está en la hora UTC.
Estoy almacenando la configuración regional de los usuarios utilizando el formato ActiveSupport: TimeZone como US/Eastern
Tengo una función de fecha de interpretación que puede explicar una cadena de fecha que no contiene de forma nativa una zona horaria o un desplazamiento
# date_str looks something like this `12/31/2014 6:22 PM`
# tz is a string containing an ActiveSupport::TimeZone name like `US/Eastern`
def interpret_date(date_str, tz)
Time.use_zone tz do
parsed_dt = DateTime.strptime(date_str, ''%m/%d/%Y %H:%M %p'')
offset = parsed_dt.in_time_zone(tz).utc_offset
parsed_dt.advance(seconds: -(offset)).in_time_zone(tz)
end
end
Esto, por supuesto, no se siente como una solución perfecta, pero funciona. Los pasos para asegurar la correcta zona horaria son:
- Analizar la cadena de fecha de acuerdo a nuestro formato.
- Extraiga el desplazamiento utc actual según la hora en esa zona horaria (tenemos que hacerlo si la fecha en la que está analizando está en dst, mientras que su hora local no es dst para la hora del este, esto asegurará que la dst sea local a la analizada hora)
- Convierta el tiempo a nuestra configuración regional de destino y permita que cambie el tiempo con el desplazamiento correcto agregado
Lo uso de la siguiente manera:
ruby-1.9.2-head :001 > require ''date''
=> true
ruby-1.9.2-head :002 > fmt = "%m-%d-%Y %H:%M:%S %Z"
=> "%m-%d-%Y %H:%M:%S %Z"
ruby-1.9.2-head :003 > DateTime.strptime "10-26-2011 10:16:29 CET", fmt
=> #<DateTime: 2011-10-26T10:16:29+01:00 (212186380589/86400,1/24,2299161)>
ruby-1.9.2-head :004 > DateTime.strptime "10-26-2011 10:16:29 UTC", fmt
=> #<DateTime: 2011-10-26T10:16:29+00:00 (212186384189/86400,0/1,2299161)>
ruby-1.9.2-head :005 > DateTime.strptime "10-26-2011 10:16:29 PST", fmt
=> #<DateTime: 2011-10-26T10:16:29-08:00 (212186412989/86400,-1/3,2299161)>
¿Es lo que quieres decir?
No puedo borrar una respuesta aceptada
Como @LeeJarvis señaló en la sección de comentarios a continuación, Chronic.parse
no acepta una opción :time_class
. Así que esta respuesta tiene errores y, aunque parece que funciona, no lo hace (a menos que Chronic permita pasar una opción de :time_class
pronto).
La gema crónica es realmente poderosa.
Lo uso así:
Chronic.parse("next 5:00 pm",
:time_class => ActiveSupport::TimeZone.new(User.first.time_zone)).utc
En mi ejemplo anterior, User.first.time_zone
es "Hora del Pacífico (EE. UU. Y Canadá)".
Chronic es compatible con muchos formatos, así que échale un vistazo en https://github.com/mojombo/chronic
Asegúrese de pasar la opción :time_class
y de convertir a UTC (en la documentación, Chronic sets :time_class
antes de llamar a parse. :time_class
este enfoque porque puede hacer que otras partes de la aplicación no funcionen)
La documentación de TimeZone se encuentra en http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html
Para pre Rails 5, funcionará lo siguiente:
t0 = Time.strptime("2018-05-01", "%Y-%m-%d") # Or whatever
t1 = Time.zone.now.change(year: t0.year, month: t0.month, day: t0.day, hour: t0.hour, min: t0.min, sec: t0.sec, usec: t0.usec)
Puede convertir la fecha a la zona horaria con to_time_in_current_zone
. Por ejemplo:
Date.strptime(''04/07/1988'', ''%m/%d/%Y'').to_time_in_current_zone
Yo estaba "strptiming" esta cadena:
19/05/2014 8:13:26 a.m.
Una marca de tiempo local, que en mi caso es Auckland NZ sin un sello de zona horaria en la cadena como se puede ver.
Lo mejor que puedo decir es que Time.strptime
usa la zona horaria del servidor como base.
En mi situación, y una buena práctica general, mis servidores se ejecutan en la zona horaria UTC, por lo que cada análisis de la cadena terminó creando un objeto de tiempo, el tiempo a +0000 (UTC):
2014-05-19 08:13:26 +0000
Luego la conversión a in_time_zone(Time.zone)
dio:
Mon, 19 May 2014 20:13:26 NZST +12:00
Lo que se puede ver es 12 horas (la compensación UTC) más tarde de la hora real que quería.
Intenté usar el truco +'' Auckland'', ''...%Z''
como se indica arriba sin ningún cambio. Luego utilicé el + ''+1200'', ''...%Z''
como anteriormente, que funcionó correctamente.
Sin embargo, estaba preocupado por el horario de verano, entonces el análisis se agotaría en una hora, así que esto es lo que he encontrado con:
Time.strptime(call_data[:datetime].gsub(//./, "").gsub(/am/, "AM").gsub(/pm/, "PM") + (Time.zone.now.time_zone.utc_offset/3600).to_s.ljust(4,''0'').rjust(6,'' +''), ''%d/%m/%Y %I:%M:%S %p %Z'').in_time_zone(Time.zone)
Resultado:
Mon, 19 May 2014 08:13:26 NZST +12:00
No es particularmente elegante pero funciona.