times rails metodo hora formato formatear fechas fecha actual ruby-on-rails ruby-on-rails-3 postgresql datetime timezone

ruby-on-rails - rails - metodo times ruby



Ignorando las zonas horarias por completo en Rails y PostgreSQL (2)

Estoy lidiando con las fechas y horas en Rails y Postgres y me encuentro con este problema:

La base de datos está en UTC.

El usuario establece una zona horaria de elección en la aplicación Rails, pero solo se debe usar para obtener la hora local de los usuarios para comparar los tiempos.

El usuario almacena una hora, por ejemplo, el 17 de marzo de 2012, a las 7 p.m. No quiero que se almacenen las conversiones de zona horaria o la zona horaria. Solo quiero que se guarden la fecha y la hora. De esta forma, si el usuario cambia su zona horaria, todavía se mostraría el 17 de marzo de 2012, a las 7 p.m.

Solo uso el huso horario especificado por los usuarios para obtener registros ''antes'' o ''después'' de la hora actual en la zona horaria local del usuario.

Actualmente estoy usando ''marca de tiempo sin zona horaria'' pero cuando recupero los registros, los rieles (?) Los convierte a la zona horaria en la aplicación, lo que no quiero.

Appointment.first.time => Fri, 02 Mar 2012 19:00:00 UTC +00:00

Debido a que los registros en la base de datos parecen ser UTC, mi truco es tomar la hora actual, eliminar la zona horaria con ''Date.strptime (str, "% m /% d /% Y")'' y luego hacer mi consulta con eso:

.where("time >= ?", date_start)

Parece que debe haber una forma más sencilla de ignorar las zonas horarias. ¿Algunas ideas?


La timestamp tipo de datos es el nombre abreviado de la timestamp without time zone de timestamp without time zone .
La otra opción timestamp es la abreviatura de la timestamp with time zone de timestamp with time zone .

timestamptz es el tipo preferido en la familia de fecha / hora, literalmente. Tiene el conjunto typispreferred en pg_type , que puede ser relevante:

Época

Internamente , las marcas de tiempo se almacenan como un recuento de una epoch . Postgres usa una época del primer momento del primer día del año 2000 en UTC, es decir, 2000-01-01T00: 00: 00Z. Ocho octets se usan para almacenar el número de conteo. Dependiendo de una opción de tiempo de compilación, ese número es:

  • Un entero de 8 bytes (predeterminado), con 0 a 6 dígitos de una fracción de segundo
  • Un número de coma flotante (en desuso), con 0 a 10 dígitos de una fracción de segundo, donde la precisión se degrada rápidamente para valores más alejados de la época.
    Las instalaciones modernas de Postgres usan el entero de 8 bytes.

Tenga en cuenta que Postgres no usa el tiempo de Unix . La época de Postgres es el primer momento de 2000-01-01 en lugar de Unix ''1970-01-01. Mientras que el tiempo de Unix tiene una resolución de segundos enteros, Postgres guarda fracciones de segundos.

timestamp

Si define un tipo de datos timestamp [without time zone] le está diciendo a Postgres: "No proporciono un huso horario explícitamente, supongo que el huso horario actual." Postgres guarda el timestamp como está - ignorando un modificador de huso horario si usted debería agregar ¡uno!

Cuando luego muestra esa timestamp , obtiene lo que ingresó literalmente. Con la misma configuración de zona horaria, todo está bien. Si la configuración de la zona horaria de la sesión cambia, también lo hace el significado de la timestamp de timestamp : el valor permanece igual.

timestamptz

El manejo de la timestamp with time zone de timestamp with time zone es sutilmente diferente. Cito el manual aquí :

Para la timestamp with time zone , el valor almacenado internamente siempre está en UTC (Tiempo coordinado universal ...)

Negrita énfasis mío. La zona horaria en sí nunca se almacena . Es un modificador de entrada que se utiliza para calcular la marca de tiempo UTC correspondiente, que se almacena, o el modificador de salida utilizado para calcular la hora local para mostrar, con el desfase de zona horaria adjunto. Si no agrega un desplazamiento para timestamptz en la entrada, se asume la configuración de la zona horaria actual de la sesión. Todos los cálculos se realizan con los valores de marca de tiempo UTC. Si tiene que (o debe) tratar con más de una zona horaria, use timestamptz .

Los clientes como psql o pgAdmin o cualquier aplicación que se comunique a través de libpq (como Ruby con la gema pg) se presentan con la marca de tiempo más la compensación para la zona horaria actual o según una zona horaria solicitada (consulte a continuación). Siempre es el mismo momento , solo varía el formato de visualización. O, como dice el manual :

Todas las fechas y horas con reconocimiento de zona horaria se almacenan internamente en UTC. Se convierten a la hora local en la zona especificada por el parámetro de configuración TimeZone antes de mostrarse al cliente.

Considere este simple ejemplo (en psql):

db=# SELECT timestamptz ''2012-03-05 20:00+03''; timestamptz ------------------------ 2012-03-05 18:00:00+01

Negrita énfasis mío. ¿Lo que pasó aquí?
Elegí un desplazamiento de zona horaria arbitraria +3 para el literal de entrada. Para Postgres, esta es solo una de las muchas maneras de ingresar la marca de tiempo UTC 2012-03-05 17:00:00 . El resultado de la consulta se muestra para el huso horario actual de Viena / Austria en mi prueba, que tiene un desplazamiento +1 durante el invierno y +2 durante el horario de verano: 2012-03-05 18:00:00+01 , porque cae en invierno.

Postgres ya ha olvidado cómo se ha ingresado este valor. Todo lo que recuerda es el valor y el tipo de datos. Al igual que con un número decimal. numeric ''003.4'' , numeric ''3.40'' o numeric ''+3.4'' - todos dan como resultado exactamente el mismo valor interno.

AT TIME ZONE

Tan pronto como capte esta lógica, puede hacer lo que quiera. Todo lo que falta ahora es una herramienta para interpretar o representar literales de marcas de tiempo de acuerdo con un huso horario específico. Ahí es donde entra la construcción AT TIME ZONE . Hay dos casos de uso diferentes. timestamptz se convierte en timestamp de timestamp y viceversa.

Para ingresar al UTC timestamptz 2012-03-05 17:00:00+0 :

SELECT timestamp ''2012-03-05 17:00:00'' AT TIME ZONE ''UTC''

... que es equivalente a:

SELECT timestamptz ''2012-03-05 17:00:00 UTC''

Para mostrar el mismo punto en el tiempo que EST timestamp (Eastern Standard Time):

SELECT timestamp ''2012-03-05 17:00:00'' AT TIME ZONE ''UTC'' AT TIME ZONE ''EST''

Así es, AT TIME ZONE ''UTC'' dos veces . El primero interpreta el valor de la timestamp como (dado) sello de tiempo UTC que devuelve el tipo timestamptz . El segundo convierte el timestamptz a la timestamp de timestamp en el huso horario dado ''EST'': lo que un reloj en la zona horaria EST muestra en este punto único en el tiempo.

Ejemplos

SELECT ts AT TIME ZONE ''UTC'' FROM ( VALUES (1, timestamptz ''2012-03-05 17:00:00+0'') , (2, timestamptz ''2012-03-05 18:00:00+1'') , (3, timestamptz ''2012-03-05 17:00:00 UTC'') , (4, timestamp ''2012-03-05 11:00:00'' AT TIME ZONE ''+6'') , (5, timestamp ''2012-03-05 17:00:00'' AT TIME ZONE ''UTC'') , (6, timestamp ''2012-03-05 07:00:00'' AT TIME ZONE ''US/Hawaii'') -- ① , (7, timestamptz ''2012-03-05 07:00:00 US/Hawaii'') -- ① , (8, timestamp ''2012-03-05 07:00:00'' AT TIME ZONE ''HST'') -- ① , (9, timestamp ''2012-03-05 18:00:00+1'') -- ② loaded footgun! ) t(id, ts);

Devuelve 8 (o 9) filas idénticas con columnas timestamptz que contienen la misma marca de tiempo UTC 2012-03-05 17:00:00 . La novena fila parece funcionar en mi zona horaria, pero es una trampa malvada. Vea abajo.

① Las filas 6 a 8 con el nombre de la zona horaria y la abreviatura de zona horaria para el horario de Hawai están sujetas a horario de verano (horario de verano) y pueden diferir, aunque no actualmente. Un nombre de zona horaria como ''US/Hawaii'' conoce las reglas de DST y todos los cambios históricos de forma automática, mientras que una abreviatura como HST es simplemente un código tonto para una compensación fija. Es posible que necesite agregar una abreviatura diferente para el verano / hora estándar. El nombre interpreta correctamente cualquier marca de tiempo en la zona horaria dada. Una abreviatura es barata, pero debe ser la correcta para la marca de tiempo determinada:

El horario de verano no está entre las ideas más brillantes que la humanidad haya tenido.

② La fila 9, marcada como footgun cargado funciona para mí , pero solo por coincidencia. Si explícitamente echas un literal a la timestamp [without time zone] de timestamp [without time zone] , se ignorará cualquier desplazamiento de zona horaria . Solo se usa la marca de tiempo simple. El valor se fuerza automáticamente a timestamptz en el ejemplo para que coincida con el tipo de columna. Para este paso, se supone la configuración de la timezone de la sesión actual, que resulta ser la misma zona horaria +1 en mi caso (Europa / Viena). Pero probablemente no en su caso, lo que dará como resultado un valor diferente. En resumen: No timestamptz literales timestamp en timestamp o pierda el desplazamiento de la zona horaria.

Tus preguntas

El usuario almacena una hora, por ejemplo, el 17 de marzo de 2012, a las 7 p.m. No quiero que se almacenen las conversiones de zona horaria o la zona horaria.

La zona horaria en sí nunca se almacena. Use uno de los métodos anteriores para ingresar una marca de tiempo UTC.

Solo uso el huso horario especificado por los usuarios para obtener registros ''antes'' o ''después'' de la hora actual en la zona horaria local del usuario.

Puede usar una consulta para todos los clientes en diferentes zonas horarias.
Por tiempo global absoluto:

SELECT * FROM tbl WHERE time_col > (now() AT TIME ZONE ''UTC'')::time

Por tiempo según el reloj local:

SELECT * FROM tbl WHERE time_col > now()::time

No está cansado de la información de fondo, sin embargo? timestamp


Si quieres negociar en UTC por defecto:

En config/application.rb , agregue:

config.time_zone = ''UTC''

Luego, si almacena el usuario actual, el nombre de la zona horaria es current_user.timezone y puede decirlo.

post.created_at.in_time_zone(current_user.timezone)

current_user.timezone debe ser un nombre de zona horaria válido; de lo contrario, obtendrás ArgumentError: Invalid Timezone , mira la lista completa .