times metodo hora formato formatear fecha actual ruby-on-rails timezone

ruby on rails - metodo - Guardar fecha y hora en UTC no es preciso a veces



metodo times ruby (3)

En general, la mejor práctica cuando se trata de fechas es almacenarlas en UTC y convertir de nuevo a lo que el usuario espera dentro de la capa de la aplicación.

Eso no necesariamente funciona con fechas futuras, particularmente cuando la fecha / hora es específica para el usuario en su zona horaria.

En mi caso, hay un atributo que es una marca de tiempo que contiene el tiempo de inicio de un evento futuro, en comparación con todo lo demás que es ahora o en el pasado (incluidas las updated_at tiempo created_at y updated_at ).

Por qué

Este campo en particular es la marca de tiempo de un evento futuro donde el usuario selecciona la hora.

Para eventos futuros, parece que la mejor práctica es no almacenar UTC .

En lugar de guardar el tiempo en UTC junto con la zona horaria, los desarrolladores pueden guardar lo que el usuario espera que guardemos: el tiempo de pared.

Cuando el usuario elige las 10 a.m., debe permanecer a las 10 a.m., incluso cuando el desplazamiento del usuario desde el UTC cambia entre la creación y la fecha del evento debido a los ahorros de luz diurna.

Por lo tanto, en junio de 2016, si un usuario crea un evento para el 1 de enero de 2017 a medianoche en Sydney, esa marca de tiempo se almacenará en la base de datos como 2017-01-01 00:00 . El desplazamiento en el momento de la creación sería +10: 00, pero en el momento del evento, sería +11: 00 ... a menos que el gobierno decida cambiar eso mientras tanto.

De la misma manera, espero que un evento separado que creo para el 1 de enero de 2016 a la medianoche en Brisbane también se almacene como 2017-01-01 00:00 . Guardo la zona horaria, es decir, Australia/Brisbane en un campo separado.

¿Cuál es la mejor forma de hacer esto en Rails?

He intentado muchas opciones sin éxito:

1. Omitir la conversión

Problema, esto solo omite la conversión en lectura , no escritura.

self.skip_time_zone_conversion_for_attributes = [:start_time]

2. Cambie toda la configuración de la aplicación para usar config.default_timestamp :local

Para hacer esto, configuro:

config / application.rb

config.active_record.default_timezone = :local config.time_zone = ''UTC''

app / model / event.rb

... self.skip_time_zone_conversion_for_attributes = [:start_time] before_save :set_timezone_to_location after_save :set_timezone_to_default def set_timezone_to_location Time.zone = location.timezone end def set_timezone_to_default Time.zone = ''UTC'' end ...

Para ser sincero, no estoy seguro de lo que está haciendo ... pero no de lo que quiero.

Pensé que estaba funcionando ya que mi evento de Brisbane se almacenó como el 2017-01-01 00:00 pero cuando creé un nuevo evento para Sydney, se almacenó como 2017-01-01 01:00 a pesar de que se muestra como medianoche correctamente en la vista.

Siendo ese el caso, me preocupa que todavía tenga el mismo problema con el evento de Sydney que estoy tratando de evitar.

3. Anule el getter y el setter para que el modelo se almacene como un entero

Intenté también almacenar el evento start_time como un entero en la base de datos.

Intenté hacer esto al poner parches en la clase Time y agregando una devolución de llamada before_validates para hacer la conversión.

config / initializers / time.rb

class Time def time_to_i self.strftime(''%Y%m%d%H%M'').to_i end end

app / model / event.rb

before_validation :change_start_time_to_integer def change_start_time_to_integer start_time = start_time.to_time if start_time.is_a? String start_time = start_time.time_to_i end # read value from DB # TODO: this freaks out with an error currently def start_time #take integer YYYYMMDDHHMM and convert it to timestamp st = self[:start_time] Time.new( st / 100000000, st / 1000000 % 100, st / 10000 % 100, st / 100 % 100, st % 100, 0, offset(true) ) end

Solución ideal

Me gustaría poder almacenar una marca de tiempo en su tipo de datos naturales en la base de datos para que las consultas no se compliquen en mis controladores, pero no puedo encontrar la manera de almacenar el "tiempo de pared" que no se convierte.

En segundo lugar, me conformaría con la opción de enteros si es necesario.

¿Cómo lidian otros con esto? ¿Qué me estoy perdiendo? Particularmente con la opción de "conversión de enteros" anterior, estoy haciendo las cosas mucho más complicadas de lo que deben ser.


Voto la ruta más simple y eso es guardar a UTC. Cree una columna para el país de origen, configure una alerta de Google para "horario de verano" y, si Chile decide dejar de usar el horario de verano o lo altera de alguna manera alocada, puede adaptarse consultando su base de datos para usuarios chilenos y ajustando su fechas de acuerdo con un script. Luego, puede usar Time.parse con la fecha, la hora y la zona horaria. Para ilustrar, aquí están los resultados el día antes del horario de verano y después del horario de verano el 13/03/2016:

Time.parse("2016-03-12 12:00:00 Pacific Time (US & Canada)").utc => 2016-03-12 20:00:00 UTC Time.parse("2016-03-14 12:00:00 Pacific Time (US & Canada)").utc => 2016-03-14 19:00:00 UTC

Esto le dará una lista de los nombres de zona horaria aceptados:

timezones = ActiveSupport::TimeZone.zones_map.values.collect{|tz| tz.name}


Propongo que sigas usando la primera opción pero con un pequeño truco: en esencia, puedes desactivar la conversión de zona horaria para el atributo deseado y usar un setter personalizado para superar la conversión durante las escrituras de atributos.

El truco ahorra tiempo como una hora UTC falsa . Aunque técnicamente tiene una zona UTC (ya que todos los tiempos se guardan en db en UTC), pero por definición se interpretará como hora local, independientemente de la zona horaria actual.

class Model < ActiveRecord::Base self.skip_time_zone_conversion_for_attributes = [:start_time] def start_time=(time) write_attribute(:start_time, time ? time + time.utc_offset : nil) end end

Probemos esto en la consola de rieles:

$ rails c >> future_time = Time.local(2020,03,30,11,55,00) => 2020-03-30 11:55:00 +0200 >> Model.create(start_time: future_time) D, [2016-03-15T00:01:09.112887 #28379] DEBUG -- : (0.1ms) BEGIN D, [2016-03-15T00:01:09.114785 #28379] DEBUG -- : SQL (1.4ms) INSERT INTO `models` (`start_time`) VALUES (''2020-03-30 11:55:00'') D, [2016-03-15T00:01:09.117749 #28379] DEBUG -- : (2.7ms) COMMIT => #<Model id: 6, start_time: "2020-03-30 13:55:00">

Tenga en cuenta que Rails guardó el tiempo como 11:55, en una zona UTC "falsa".

También tenga en cuenta que el tiempo en el objeto devuelto por create es incorrecto porque la zona se convierte de "UTC" en este caso. Tendría que contar con eso y volver a cargar el objeto cada vez después de configurar el atributo start_time , de modo que la conversión de zona salte pueda tener lugar:

>> m = Model.create(start_time: future_time).reload D, [2016-03-15T00:08:54.129926 #28589] DEBUG -- : (0.2ms) BEGIN D, [2016-03-15T00:08:54.131189 #28589] DEBUG -- : SQL (0.7ms) INSERT INTO `models` (`start_time`) VALUES (''2020-03-30 11:55:00'') D, [2016-03-15T00:08:54.134002 #28589] DEBUG -- : (2.5ms) COMMIT D, [2016-03-15T00:08:54.141720 #28589] DEBUG -- : Model Load (0.3ms) SELECT `models`.* FROM `models` WHERE `models`.`id` = 10 LIMIT 1 => #<Model id: 10, start_time: "2020-03-30 11:55:00"> >> m.start_time => 2020-03-30 11:55:00 UTC

Después de cargar el objeto, el atributo start_time es correcto y puede interpretarse manualmente como hora local, independientemente de la zona horaria real.

Realmente no entiendo por qué Rails se comporta de la manera que lo hace con respecto a la opción de configuración skip_time_zone_conversion_for_attributes ...

Actualización: agregar un lector

También podemos agregar un lector para que interpretemos automáticamente la hora UTC "falsa" guardada en la hora local, sin cambiar el tiempo debido al cambio de zona horaria:

class Model < ActiveRecord::Base # interprets time stored in UTC as local time without shifting time # due to time zone change def start_time t = read_attribute(:start_time) t ? Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec) : nil end end

Prueba en la consola de rieles:

>> m = Model.create(start_time: future_time).reload D, [2016-03-15T08:10:54.889871 #28589] DEBUG -- : (0.1ms) BEGIN D, [2016-03-15T08:10:54.890848 #28589] DEBUG -- : SQL (0.4ms) INSERT INTO `models` (`start_time`) VALUES (''2020-03-30 11:55:00'') D, [2016-03-15T08:10:54.894413 #28589] DEBUG -- : (3.1ms) COMMIT D, [2016-03-15T08:10:54.895531 #28589] DEBUG -- : Model Load (0.3ms) SELECT `models`.* FROM `models` WHERE `models`.`id` = 12 LIMIT 1 => #<Model id: 12, start_time: "2020-03-30 11:55:00"> >> m.start_time => 2020-03-30 11:55:00 +0200

Es decir, start_time se interpreta correctamente en la hora local, aunque se almacenó como la misma hora y minuto, pero en UTC.


Esto puede sonar un poco por ahí, pero he tratado con problemas similares con una aplicación reciente que tuve la tarea, pero en el lado opuesto, cuando ejecuto un ETL para cargar datos para la aplicación, las fechas de la fuente se almacenan en EST . Rails cree que es UTC cuando se sirven los datos, así que para eso, convertí las fechas a UTC usando P / SQL. No quería que estas fechas fueran diferentes a los otros campos de fecha dentro de la aplicación.

Opción A En este caso, ¿podría capturar la zona horaria del usuario en la creación y enviarla de vuelta como un campo oculto en el formulario? Todavía estoy aprendiendo RoR, así que no estoy seguro de la forma "correcta" de hacer esto, pero ahora haría algo como esto:

Ejemplo (Probé esto, y enviará el desplazamiento (minutos) en un campo oculto):

<div class="field"> <%= f.hidden_field :offset %> </div> <script> utcDiff = new Date().getTimezoneOffset(); dst_field = document.getElementById(''timepage_offset''); dst_field.value = utcDiff; </script>

Si luego envía utcDiff junto con la fecha seleccionada por el usuario, puede calcular la fecha UTC antes de almacenar. Supongo que también podría agregar eso al modelo si esa información es necesaria para saber más adelante.

Creo que no importa cómo se haga esto, siempre habrá una pequeña área de confusión, a menos que el usuario sea capaz de proporcionar la información adecuada, lo que me lleva a ...

Opción B: podría, en lugar de un campo oculto, proporcionar una lista de selección (y para ser amigable, por defecto para el desplazamiento local de los usuarios), para permitirles proporcionar la zona para la que se especifica su fecha.

Actualización: selección de TimeZone He realizado algunas investigaciones, y parece que ya hay un ayudante de formulario para un cuadro de selección de zona horaria.