ruby on rails - timezones - ¿Alguien sabe cómo lidiar adecuadamente con las zonas horarias de los usuarios en los rieles 2.3?
time rails (3)
Tuve este problema cuando estaba construyendo un sistema de tickets de soporte. Mi solución fue la siguiente.
Establezca el huso horario de Datetime SQL y ActiveRecord en UTC.
Establezca el formato de hora deseado en la configuración de su aplicación.
Haga una clase de CSS sobre cómo desea que se vea el CSS de su fecha. Haz el nombre Único. EG UTCDate
Escriba javascript para buscar en la página las clases de DOM de fecha nombradas (UTCDate desde arriba). Como Javascript es ejecutado por el usuario final, reemplazará el valor con el que está configurado el equipo local. En caso de que tenga un error de análisis, deje la hora en UTC. Mire aquí javascript relevante
La ventaja de esta solución es que su aplicación no tiene un selector de zonas horarias estúpido como lo hacen muchos foros. También disminuye la carga del servidor y el tamaño de la base de código (ya que no tiene que almacenar o manejar ninguna información sobre las zonas horarias del cliente) para manejar una tarea importante pero difícil.
Estamos construyendo una aplicación de rieles que necesita mostrar fechas (y más importante aún, calcularlas) en múltiples zonas horarias.
¿Alguien puede indicarme cómo trabajar con zonas horarias de usuario en los carriles 2.3 (.5 o .8)
El artículo más completo que he visto que detalla cómo se supone que funcionan las zonas horarias de los usuarios está aquí: http://wiki.rubyonrails.org/howtos/time-zones ... aunque no está claro cuándo se escribió esto o para qué versión de rieles. Específicamente declara que:
"Time.zone: el huso horario que se usa para mostrar. Esto se puede configurar manualmente para anular config.time_zone según cada solicitud".
Términos de claves que son "con fines de visualización" y "por solicitud".
Localmente en mi máquina, esto es cierto. Sin embargo, en la producción, ninguno es verdad. La configuración de Time.zone persiste después del final de la solicitud (a todas las solicitudes posteriores) y también afecta la forma en que AR guarda el DB (básicamente trata cualquier fecha como si ya estuviera en UTC, incluso cuando no lo está), guardando así valores completamente inapropiados .
Ejecutamos Ruby Enterprise Edition en producción con pasajeros. Si este es mi problema, ¿necesitamos cambiar a JRuby o algo más?
Para ilustrar el problema, coloqué las siguientes acciones en mi ApplicationController ahora mismo:
def test
p_time = Time.now.utc
s_time = Time.utc(p_time.year, p_time.month, p_time.day, p_time.hour)
logger.error "TIME.ZONE" + Time.zone.inspect
logger.error ENV[''TZ''].inspect
logger.error p_time.inspect
logger.error s_time.inspect
jl = JunkLead.create!
jl.date_at = s_time
logger.error s_time.inspect
logger.error jl.date_at.inspect
jl.save!
logger.error s_time.inspect
logger.error jl.date_at.inspect
render :nothing => true, :status => 200
end
def test2
Time.zone = ''Mountain Time (US & Canada)''
logger.error "TIME.ZONE" + Time.zone.inspect
logger.error ENV[''TZ''].inspect
render :nothing => true, :status => 200
end
def test3
Time.zone = ''UTC''
logger.error "TIME.ZONE" + Time.zone.inspect
logger.error ENV[''TZ''].inspect
render :nothing => true, :status => 200
end
y producen lo siguiente:
Processing ApplicationController#test (for 98.202.196.203 at 2010-12-24 22:15:50) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c57a68 @tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, @name="UTC", @utc_offset=0>
nil
Fri Dec 24 22:15:50 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 22:00:00 UTC +00:00
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 22:00:00 UTC +00:00
Completed in 21ms (View: 0, DB: 4) | 200 OK [http://www.dealsthatmatter.com/test]
Processing ApplicationController#test2 (for 98.202.196.203 at 2010-12-24 22:15:53) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c580a8 @tzinfo=#<TZInfo::DataTimezone: America/Denver>, @name="Mountain Time (US & Canada)", @utc_offset=-25200>
nil
Completed in 143ms (View: 1, DB: 3) | 200 OK [http://www.dealsthatmatter.com/test2]
Processing ApplicationController#test (for 98.202.196.203 at 2010-12-24 22:15:59) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c580a8 @tzinfo=#<TZInfo::DataTimezone: America/Denver>, @name="Mountain Time (US & Canada)", @utc_offset=-25200>
nil
Fri Dec 24 22:15:59 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 15:00:00 MST -07:00
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 15:00:00 MST -07:00
Completed in 20ms (View: 0, DB: 4) | 200 OK [http://www.dealsthatmatter.com/test]
Processing ApplicationController#test3 (for 98.202.196.203 at 2010-12-24 22:16:03) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c57a68 @tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, @name="UTC", @utc_offset=0>
nil
Completed in 17ms (View: 0, DB: 2) | 200 OK [http://www.dealsthatmatter.com/test3]
Processing ApplicationController#test (for 98.202.196.203 at 2010-12-24 22:16:04) [GET]
TIME.ZONE#<ActiveSupport::TimeZone:0x2c57a68 @tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, @name="UTC", @utc_offset=0>
nil
Fri Dec 24 22:16:05 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 22:00:00 UTC +00:00
Fri Dec 24 22:00:00 UTC 2010
Fri, 24 Dec 2010 22:00:00 UTC +00:00
Completed in 151ms (View: 0, DB: 4) | 200 OK [http://www.dealsthatmatter.com/test]
Debe quedar claro que la segunda llamada / prueba muestra Time.zone establecido en Mountain, aunque no debería.
Además, al verificar la base de datos, se revela que la acción de prueba, cuando se ejecutó después de test2, guardó un registro de JunkLead con una fecha de 2010-12-22 15:00:00, lo cual es claramente incorrecto.
Después de una exhaustiva investigación, ahora está COMPLETAMENTE claro que Time.zone está roto en la mayoría de las versiones de Rails (incluidos 2.3 y 3). Esta función utiliza un hash de hilo central para almacenar el valor que está establecido (que se supone que es seguro para subprocesos, y no lo es) y termina modificando el comportamiento para todas las solicitudes posteriores. Además, contrariamente a la documentación, la configuración de Time.zone modifica el comportamiento de ActiveRecord y guarda los tiempos de las fechas en la zona nueva, en lugar de la especificada en config (que generalmente es UTC).
Hasta que Rails lo solucione, elegimos trabajar con zonas horarias manualmente, a las que se puede acceder a través del método predeterminado no documentado:
ActiveSupport::TimeZone[''Arizona''].now (or .local, or .parse).
Además, paré Time y ActiveSupport :: TimeWithZone para proporcionar una conversión fácil de un momento a una zona diferente. Para ser claro, quiero decir el momento de tiempo correspondiente en una zona diferente, no el momento simultáneo.
>> Time.utc(2011)
=> Sat Jan 01 00:00:00 UTC 2011
>> Time.utc(2011).in_time_zone(''Arizona'')
=> Fri, 31 Dec 2010 17:00:00 MST -07:00 #Simultaneous
>> Time.utc(2011).change_zone(''Arizona'')
=> Sat, 01 Jan 2011 00:00:00 MST -07:00 #Corresponding
El parche es el siguiente:
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Time #:nodoc:
module ZoneCalculations
def self.included(base) #:nodoc:
base.class_eval do
alias_method_chain :change, :zone
end
end
def change_zone(new_zone)
new_zone = ::Time.__send__(:get_zone, new_zone)
return self if new_zone.name == zone
new_zone.local(year,month,day,hour,min,sec,usec)
end
def change_with_zone(options)
result = change_without_zone(options)
options[:zone] ? result.change_zone(options[:zone]) : result
end
end
end
end
class TimeWithZone
def change_zone(new_zone)
time.change_zone(new_zone)
end
def change(options)
time.change(options)
end
end
end
class Time
include ActiveSupport::CoreExtensions::Time::ZoneCalculations
end
Tiene razón en que Rails no restablece automáticamente la zona horaria por solicitud. El autor de la wiki da un ejemplo donde la zona horaria se establece en un filtro anterior, por lo que nunca experimenta el problema con las zonas horarias "filtradas" entre las solicitudes (ya que la zona horaria está configurada correctamente antes de la solicitud). El ejemplo utilizado en la documentación de TimeZone.zone=
para TimeZone.zone=
es similar. Entonces creo que es un problema de documentación.
El cambio del huso horario no es local, es thread local. Rails almacena la zona horaria seleccionada en la Thread.current.[:time_zone]
actual (consulte Thread.current.[:time_zone]
), no en la solicitud actual. Como el mismo subproceso maneja múltiples solicitudes, los cambios en Time.zone son persistentes entre las solicitudes.
Creo que la forma correcta de usar zonas horarias en su escenario es usar Time.use_zone
:
def my_action
Time.zone # ''UTC''
Time.use_zone(''Mountain Time (US & Canada)'') do
Time.zone # ''Mountain Time (US & Canada)''
end
Time.zone # ''UTC''
end
No he tenido tiempo de revisar sus problemas con ActiveRecord. Volveré con una actualización cuando lo haya hecho.