Estoy usando la zona horaria de América / Nueva York. En el otoño "retrocedemos" una hora - efectivamente "ganando" una hora a las 2 am. En el punto de transición sucede lo siguiente:
son las 01:59:00 -04: 00 y
luego 1 minuto después se convierte en:
01:00:00 -05: 00
Entonces, si simplemente dice "1:30 am", es ambiguo si se está refiriendo o no a la primera vez que llega la 1:30 o la segunda. Estoy tratando de guardar los datos de programación en una base de datos MySQL y no puedo determinar cómo guardar los tiempos correctamente.
Aquí está el problema:
"2009-11-01 00:30:00" se almacena internamente como 2009-11-01 00:30:00 -04: 00
"2009-11-01 01:30:00" se almacena internamente como 2009-11-01 01:30:00 -05: 00
Esto está bien y es bastante esperado. Pero, ¿cómo puedo guardar nada para 01:30:00 -04: 00 ? La documentación no muestra ningún soporte para especificar el desplazamiento y, en consecuencia, cuando he intentado especificar el desplazamiento, se ha ignorado debidamente.
Las únicas soluciones en las que he pensado implican configurar el servidor en una zona horaria que no use el horario de verano y hacer las transformaciones necesarias en mis scripts (estoy usando PHP para esto). Pero eso no parece que deba ser necesario.
Muchas gracias por las sugerencias.
Respuestas:
Los tipos de fecha de MySQL están, francamente, rotos y no pueden almacenar todas las horas correctamente a menos que su sistema esté configurado en una zona horaria de desplazamiento constante, como UTC o GMT-5. (Estoy usando MySQL 5.0.45)
Esto se debe a que no puede almacenar ningún tiempo durante la hora antes de que finalice el horario de verano . No importa cómo ingrese las fechas, cada función de fecha tratará estos tiempos como si fueran durante la hora posterior al cambio.
La zona horaria de mi sistema es
America/New_York
. Intentemos almacenar 1257051600 (domingo, 01 de noviembre de 2009, 06:00:00 +0100).Aquí está usando la sintaxis patentada INTERVAL:
SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3599 SECOND); # 1257051599 SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3600 SECOND); # 1257055200 SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 1 SECOND); # 1257051599 SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 0 SECOND); # 1257055200
Incluso
FROM_UNIXTIME()
no devolverá la hora exacta.SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051599)); # 1257051599 SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051600)); # 1257055200
Por extraño que parezca, DATETIME será todavía almacenar y retorno (en forma de cadena única!) Veces dentro de una hora "perdido" cuando se inicia horario de verano (por ejemplo
2009-03-08 02:59:59
). Pero usar estas fechas en cualquier función de MySQL es arriesgado:SELECT UNIX_TIMESTAMP('2009-03-08 01:59:59'); # 1236495599 SELECT UNIX_TIMESTAMP('2009-03-08 02:00:00'); # 1236495600 # ... SELECT UNIX_TIMESTAMP('2009-03-08 02:59:59'); # 1236495600 SELECT UNIX_TIMESTAMP('2009-03-08 03:00:00'); # 1236495600
La conclusión: si necesita almacenar y recuperar en todas las épocas del año, tiene algunas opciones indeseables:
Almacene las fechas como INT (como descubrió Aaron, TIMESTAMP ni siquiera es confiable)
Imagina que el tipo DATETIME tiene una zona horaria de desplazamiento constante. Por ejemplo, si está dentro
America/New_York
, convierta su fecha a GMT-5 fuera de MySQL , luego almacénela como DATETIME (esto resulta ser esencial: vea la respuesta de Aaron). Entonces debe tener mucho cuidado al usar las funciones de fecha / hora de MySQL, porque algunos asumen que sus valores son de la zona horaria del sistema, otros (especialmente las funciones aritméticas de tiempo) son "independientes de la zona horaria" (pueden comportarse como si las horas fueran UTC).Aaron y yo sospechamos que las columnas TIMESTAMP autogeneradas también están rotas. Ambos
2009-11-01 01:30 -0400
y2009-11-01 01:30 -0500
se almacenarán como ambiguos2009-11-01 01:30
.fuente
select '2009-11-01 00:00:00' + INTERVAL 3600 SECOND;
cuál es'2009-11-01 01:00:00'
. UNIX_TIMESTAMP luego simplemente intenta convertir esto a UTC en el contexto de la zona horaria de la sesión; no intenta realizar la adición en el contexto de las reglas DST de esa zona horaria.America/New_York
, no veo forma de almacenar 1257051600. ¿Verdad?Lo tengo resuelto para mis propósitos. Resumiré lo que aprendí (lo siento, estas notas son detalladas; son tanto para mi futura referencia como cualquier otra cosa).
Contrariamente a lo que dije en uno de mis comentarios anteriores, los campos de fecha y hora y TIMESTAMP no se comportan de manera diferente. Los campos TIMESTAMP (como indican los documentos) toman todo lo que les envíe en formato "AAAA-MM-DD hh: mm: ss" y conviértalo de su zona horaria actual a la hora UTC. Lo contrario ocurre de forma transparente cada vez que recupera los datos. Los campos DATETIME no realizan esta conversión. Toman todo lo que les envíes y lo almacenan directamente.
Ni los tipos de campo DATETIME ni TIMESTAMP pueden almacenar datos con precisión en una zona horaria que respete el horario de verano . Si almacena "2009-11-01 01:30:00", los campos no tienen forma de distinguir qué versión de la 1:30 am deseaba: la versión -04: 00 o -05: 00.
Ok, entonces debemos almacenar nuestros datos en una zona horaria que no sea DST (como UTC). Los campos TIMESTAMP no pueden manejar estos datos con precisión por las razones que explicaré: si su sistema está configurado en una zona horaria DST, entonces lo que ingresa en TIMESTAMP puede que no sea lo que obtiene. Incluso si le envía datos que ya ha convertido a UTC, seguirá asumiendo que los datos están en su zona horaria local y hará otra conversión a UTC. Este viaje de ida y vuelta local-a-UTC-back-to-local aplicado por TIMESTAMP tiene pérdidas cuando la zona horaria local observa el horario de verano (ya que "2009-11-01 01:30:00" se asigna a 2 horas posibles diferentes).
Con DATETIME puede almacenar sus datos en cualquier zona horaria que desee y estar seguro de que recibirá lo que envíe (no se verá obligado a realizar las conversiones de ida y vuelta con pérdidas que los campos TIMESTAMP le imponen). Entonces, la solución es usar un campo DATETIME y antes de guardar en el campo, convierta la zona horaria de su sistema en cualquier zona que no sea DST en la que desee guardarlo (creo que UTC es probablemente la mejor opción). Esto le permite crear la lógica de conversión en su lenguaje de secuencias de comandos para que pueda guardar explícitamente el equivalente UTC de "2009-11-01 01:30:00 -04: 00" o "" 2009-11-01 01:30: 00-05: 00 ".
Otra cosa importante a tener en cuenta es que las funciones matemáticas de fecha / hora de MySQL no funcionan correctamente alrededor de los límites DST si almacena sus fechas en un DST TZ. Así que una razón de más para ahorrar en UTC.
En pocas palabras, ahora hago esto:
Al recuperar los datos de la base de datos:
Interprete explícitamente los datos de la base de datos como UTC fuera de MySQL para obtener una marca de tiempo Unix precisa. Utilizo la función strtotime () de PHP o su clase DateTime para esto. No se puede hacer de manera confiable dentro de MySQL usando las funciones CONVERT_TZ () o UNIX_TIMESTAMP () de MySQL porque CONVERT_TZ solo generará un valor 'YYYY-MM-DD hh: mm: ss' que sufre problemas de ambigüedad, y UNIX_TIMESTAMP () asume su La entrada está en la zona horaria del sistema, no en la zona horaria en la que los datos se almacenaron REALMENTE (UTC).
Al almacenar los datos en la base de datos:
Convierta su fecha a la hora UTC precisa que desee fuera de MySQL. Por ejemplo: con la clase DateTime de PHP puede especificar "2009-11-01 1:30:00 EST" de forma distinta a "2009-11-01 1:30:00 EDT", luego convertirlo a UTC y guardar la hora UTC correcta. a su campo DATETIME.
Uf. Muchas gracias por el aporte y la ayuda de todos. Con suerte, esto le ahorrará a alguien más dolores de cabeza en el futuro.
Por cierto, estoy viendo esto en MySQL 5.0.22 y 5.0.27
fuente
Creo que el enlace de micahwittman tiene la mejor solución práctica para estas limitaciones de MySQL: establezca la zona horaria de la sesión en UTC cuando se conecte:
SET SESSION time_zone = '+0:00'
Luego, simplemente envíele las marcas de tiempo de Unix y todo debería estar bien.
fuente
Este hilo me volvió loco ya que usamos
TIMESTAMP
columnas conOn UPDATE CURRENT_TIMESTAMP
(es decir:)recordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
para rastrear registros cambiados y ETL en un almacén de datos.En caso de que alguien se pregunte, en este caso,
TIMESTAMP
compórtese correctamente y puede diferenciar entre las dos fechas similares convirtiendo laTIMESTAMP
marca de tiempo a Unix:select TestFact.*, UNIX_TIMESTAMP(recordTimestamp) from TestFact; id recordTimestamp UNIX_TIMESTAMP(recordTimestamp) 1 2012-11-04 01:00:10.0 1352005210 2 2012-11-04 01:00:10.0 1352008810
fuente
Puede convertir a UTC como:
SELECT CONVERT_TZ('2009-11-29 01:30:00','-04:00','+00:00');
Aún mejor, guarde las fechas como un campo TIMESTAMP . Eso siempre se almacena en UTC, y UTC no conoce el horario de verano / invierno.
Puede convertir de UTC a hora local usando CONVERT_TZ :
SELECT CONVERT_TZ(UTC_TIMESTAMP(),'+00:00','SYSTEM');
Donde '+00: 00' es UTC, la zona horaria de origen y 'SYSTEM' es la zona horaria local del sistema operativo donde se ejecuta MySQL.
fuente
Mysql resuelve inherentemente este problema usando la tabla time_zone_name de mysql db. Utilice CONVERT_TZ mientras CRUD para actualizar la fecha y hora sin preocuparse por el horario de verano.
SELECT CONVERT_TZ('2019-04-01 00:00:00','Europe/London','UTC') AS time1, CONVERT_TZ('2019-03-01 00:00:00','Europe/London','UTC') AS time2;
fuente
Estaba trabajando en registrar recuentos de visitas de páginas y mostrar los recuentos en un gráfico (usando el complemento Flot jQuery). Llené la tabla con datos de prueba y todo se veía bien, pero noté que al final del gráfico los puntos tenían un día de descanso de acuerdo con las etiquetas en el eje x. Después del examen, noté que el recuento de vistas del día 2015-10-25 se recuperó dos veces de la base de datos y se pasó a Flot, por lo que todos los días posteriores a esta fecha se movieron un día a la derecha.
Después de buscar un error en mi código por un tiempo, me di cuenta de que esta fecha es cuando tiene lugar el DST. Luego llegué a esta página SO ...
... pero las soluciones sugeridas eran excesivas para lo que necesitaba o tenían otras desventajas. No me preocupa mucho no poder distinguir entre marcas de tiempo ambiguas. Solo necesito contar y mostrar registros por días.
Primero, recupero el rango de fechas:
SELECT DATE(MIN(created_timestamp)) AS min_date, DATE(MAX(created_timestamp)) AS max_date FROM page_display_log WHERE item_id = :item_id
Luego, en un bucle for, comenzando con
min_date
, terminando conmax_date
, por paso de un día (60*60*24
), estoy recuperando los recuentos:for( $day = $min_date_timestamp; $day <= $max_date_timestamp; $day += 60 * 60 * 24 ) { $query = " SELECT COUNT(*) AS count_per_day FROM page_display_log WHERE item_id = :item_id AND ( created_timestamp BETWEEN '" . date( "Y-m-d 00:00:00", $day ) . "' AND '" . date( "Y-m-d 23:59:59", $day ) . "' ) "; //execute query and do stuff with the result }
Mi solución final y rápida a mi problema fue esta:
$min_date_timestamp += 60 * 60 * 2; // To avoid DST problems for( $day = $min_date_timestamp; $day <= $max_da.....
Así que no estoy mirando el bucle al comienzo del día, sino dos horas después . El día sigue siendo el mismo, y todavía estoy recuperando los recuentos correctos, ya que solicito explícitamente a la base de datos registros entre las 00:00:00 y las 23:59:59 del día, independientemente de la hora real de la marca de tiempo. Y cuando el tiempo salta una hora, todavía estoy en el día correcto.
Nota: Sé que este es un hilo de 5 años y sé que no es una respuesta a la pregunta de OP, pero podría ayudar a personas como yo que encontraron esta página en busca de una solución al problema que describí.
fuente
"SELECT CAST(created_timestamp AS date) day,COUNT(*) WHERE item_id=:item_id AND (created_timestamp BETWEEN '".date("Y-m-d 00:00:00", $min_date_timestamp)."' AND '".date("Y-m-d 23:59:59", $max_date_timestamp)."') GROUP BY day ORDER BY day";