Estou usando o fuso horário da América / Nova York. No outono, "recuamos" uma hora - efetivamente "ganhando" uma hora às 2h. No ponto de transição acontece o seguinte:
é 01:59:00 -04: 00
e 1 minuto depois torna-se:
01:00:00 -05: 00
Portanto, se você simplesmente disser "1h30", será ambíguo se você está se referindo à primeira vez ou não à 1h30. Estou tentando salvar dados de agendamento em um banco de dados MySQL e não consigo determinar como salvar os horários corretamente.
Aqui está o problema:
"2009-11-01 00:30:00" é armazenado internamente como 2009-11-01 00:30:00 -04: 00
"2009-11-01 01:30:00" é armazenado internamente como 01-11-2009 01:30:00 -05: 00
Isso é bom e bastante esperado. Mas como faço para salvar algo em 01:30:00 -04: 00 ? A documentação não mostra nenhum suporte para a especificação do deslocamento e, portanto, quando tentei especificar o deslocamento, ele foi devidamente ignorado.
As únicas soluções que pensei envolvem definir o servidor para um fuso horário que não use o horário de verão e fazer as transformações necessárias em meus scripts (estou usando PHP para isso). Mas isso não parece ser necessário.
Muito obrigado por todas as sugestões.
Respostas:
Os tipos de data do MySQL são, francamente, quebrados e não podem armazenar todos os horários corretamente, a menos que seu sistema esteja configurado para um fuso horário de deslocamento constante, como UTC ou GMT-5. (Estou usando MySQL 5.0.45)
Isso ocorre porque você não pode armazenar qualquer hora durante a hora antes do horário de verão . Não importa como você insere as datas, cada função de data tratará esses horários como se fossem da hora após a troca.
O fuso horário do meu sistema é
America/New_York
. Vamos tentar armazenar 1257051600 (Dom, 01 de novembro de 2009 06:00:00 +0100).Aqui está usando a sintaxe proprietária INTERVAL:
Mesmo
FROM_UNIXTIME()
não retornará o tempo exato.Curiosamente, DATETIME vai ainda armazenar e retorno (na única forma corda!) Vezes dentro de uma hora "perdido" quando horário de verão começa (por exemplo
2009-03-08 02:59:59
). Mas usar essas datas em qualquer função MySQL é arriscado:Conclusão: se você precisa armazenar e recuperar todas as épocas do ano, tem algumas opções indesejáveis:
Armazene datas como INTs (como Aaron descobriu, TIMESTAMP nem é confiável)
Finja que o tipo DATETIME tem algum fuso horário de deslocamento constante. Por exemplo, se você estiver em
America/New_York
, converta sua data para GMT-5 fora do MySQL e armazene como DATETIME (isso é essencial: veja a resposta de Aaron). Então você deve tomar muito cuidado ao usar as funções de data / hora do MySQL, porque alguns assumem que seus valores são do fuso horário do sistema, outros (especialmente funções aritméticas de tempo) são "agnósticos de fuso horário" (eles podem se comportar como se os horários fossem UTC).Aaron e eu suspeitamos que as colunas TIMESTAMP de geração automática também estão quebradas. Ambos
2009-11-01 01:30 -0400
e2009-11-01 01:30 -0500
serão armazenados como ambíguos2009-11-01 01:30
.fonte
select '2009-11-01 00:00:00' + INTERVAL 3600 SECOND;
qual é'2009-11-01 01:00:00'
. UNIX_TIMESTAMP então simplesmente tenta converter isso para UTC no contexto do fuso horário da sessão - ele não tenta realizar a adição no contexto das regras de horário de verão desse fuso horário.America/New_York
, não vejo como armazenar 1257051600. Você vê?Eu tenho isso planejado para meus propósitos. Vou resumir o que aprendi (desculpe, essas notas são prolixas; são tanto para minha referência futura quanto qualquer outra coisa).
Ao contrário do que eu disse em um dos meus comentários anteriores, campos DATETIME e TIMESTAMP fazer se comportam de forma diferente. Os campos TIMESTAMP (como os documentos indicam) pegam tudo o que você enviar no formato "AAAA-MM-DD hh: mm: ss" e convertem do seu fuso horário atual para o horário UTC. O inverso acontece de forma transparente sempre que você recupera os dados. Os campos DATETIME não fazem essa conversão. Eles pegam tudo o que você envia e apenas armazenam diretamente.
Nem os tipos de campo DATETIME nem TIMESTAMP podem armazenar dados com precisão em um fuso horário que observe o DST . Se você armazenar "2009-11-01 01:30:00", os campos não terão como distinguir qual versão de 1:30 da manhã você deseja - a versão -04: 00 ou -05: 00.
Ok, então devemos armazenar nossos dados em um fuso horário diferente do horário de verão (como UTC). Os campos TIMESTAMP são incapazes de lidar com esses dados com precisão por motivos que explicarei: se o seu sistema estiver configurado para um fuso horário DST, então o que você colocar em TIMESTAMP pode não ser o que você receberá de volta. Mesmo se você enviar dados já convertidos para UTC, ele ainda assumirá os dados em seu fuso horário local e fará outra conversão para UTC. Esta viagem de ida e volta local-para-UTC-de-volta-para-local imposta por TIMESTAMP tem perdas quando seu fuso horário local segue o horário de verão (desde "2009-11-01 01:30:00" mapeia para 2 horários diferentes possíveis).
Com DATETIME você pode armazenar seus dados em qualquer fuso horário que desejar e ter a certeza de que receberá de volta tudo o que enviar (você não será forçado a fazer conversões de ida e volta com perdas que os campos do TIMESTAMP impõem a você). Portanto, a solução é usar um campo DATETIME e, antes de salvar no campo, converter do fuso horário do seu sistema para qualquer fuso horário não DST em que você deseja salvá-lo (acho que UTC é provavelmente a melhor opção). Isso permite que você construa a lógica de conversão em sua linguagem de script para que possa salvar explicitamente o equivalente UTC de "2009-11-01 01:30:00 -04: 00" ou "" 2009-11-01 01:30: 00 -05: 00 ".
Outra coisa importante a se notar é que as funções matemáticas de data / hora do MySQL não funcionam corretamente em torno dos limites do DST se você armazenar suas datas em um DST TZ. Portanto, mais um motivo para economizar em UTC.
Resumindo, agora faço o seguinte:
Ao recuperar os dados do banco de dados:
Interprete explicitamente os dados do banco de dados como UTC fora do MySQL para obter um carimbo de data / hora Unix preciso. Eu uso a função strtotime () do PHP ou sua classe DateTime para isso. Isso não pode ser feito de forma confiável dentro do MySQL usando as funções CONVERT_TZ () ou UNIX_TIMESTAMP () do MySQL porque CONVERT_TZ só produzirá um valor 'YYYY-MM-DD hh: mm: ss' que sofre de problemas de ambigüidade, e UNIX_TIMESTAMP () assume seu a entrada está no fuso horário do sistema, não no fuso horário em que os dados foram REALMENTE armazenados (UTC).
Ao armazenar os dados no banco de dados:
Converta sua data para a hora UTC precisa que você deseja fora do MySQL. Por exemplo: com a classe DateTime do PHP, você pode especificar "2009-11-01 1:30:00 EST" distintamente de "2009-11-01 1:30:00 EDT", então convertê-lo para UTC e salvar a hora UTC correta ao seu campo DATETIME.
Ufa. Muito obrigado pela contribuição e ajuda de todos. Esperançosamente, isso evita que alguém tenha algumas dores de cabeça no futuro.
BTW, estou vendo isso no MySQL 5.0.22 e 5.0.27
fonte
Acho que o link de micahwittman tem a melhor solução prática para essas limitações do MySQL: Defina o fuso horário da sessão como UTC ao conectar:
Depois, basta enviar carimbos de data / hora Unix e tudo ficará bem.
fonte
Esse tópico me deixou louco, pois usamos
TIMESTAMP
colunas comOn UPDATE CURRENT_TIMESTAMP
(ou sejarecordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
:) para rastrear registros alterados e ETL para um datawarehouse.Caso alguém se pergunte, neste caso,
TIMESTAMP
comporte-se corretamente e você pode diferenciar entre as duas datas semelhantes convertendo oTIMESTAMP
carimbo de data / hora em unix:fonte
Você pode converter para UTC como:
Melhor ainda, salve as datas como um campo TIMESTAMP . Isso sempre é armazenado no UTC, e o UTC não sabe sobre o horário de verão / inverno.
Você pode converter de UTC para hora local usando CONVERT_TZ :
Onde '+00: 00' é UTC, o fuso horário de e 'SYSTEM' é o fuso horário local do sistema operacional onde o MySQL é executado.
fonte
O Mysql inerentemente resolve este problema usando a tabela time_zone_name do mysql db. Use CONVERT_TZ enquanto CRUD para atualizar a data e hora sem se preocupar com o horário de verão.
fonte
Eu estava trabalhando no registro de contagens de visitas de páginas e exibindo as contagens em gráfico (usando o plugin Flot jQuery). Preenchi a tabela com dados de teste e tudo parecia bem, mas notei que no final do gráfico os pontos estavam um dia fora de acordo com os rótulos no eixo x. Após exame, notei que a contagem de visualizações para o dia 2015-10-25 foi recuperada duas vezes do banco de dados e passada para o Flot, portanto, todos os dias após essa data foram movidos um dia para a direita.
Depois de procurar um bug no meu código por um tempo, percebi que essa data é quando ocorre o DST. Então eu vim para esta página do SO ...
... mas as soluções sugeridas eram um exagero para o que eu precisava ou tinham outras desvantagens. Não estou muito preocupado em não ser capaz de distinguir entre carimbos de data / hora ambíguos. Eu só preciso contar e exibir os registros por dias.
Primeiro, recupero o intervalo de datas:
Então, em um loop for, começando com
min_date
, terminando commax_date
, por etapa de um dia (60*60*24
), estou recuperando as contagens:Minha solução final e rápida para o meu problema foi esta:
Portanto, não estou encarando o loop no início do dia, mas duas horas depois . O dia ainda é o mesmo, e ainda estou recuperando as contagens corretas, pois solicito explicitamente ao banco de dados os registros entre 00:00:00 e 23:59:59 do dia, independentemente da hora real do carimbo de data / hora. E quando o tempo salta uma hora, ainda estou no dia correto.
Nota: Eu sei que este é um tópico de 5 anos, e eu sei que esta não é uma resposta para a pergunta dos OPs, mas pode ajudar pessoas como eu que encontraram esta página em busca de solução para o problema que descrevi.
fonte
"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";