Como localizar datas corretamente usando Python e pytz
Sempre dá para aprender um pouco mais sobre lidar com timezones em Python.
Um jeito que eu estava usando para criar datas com fuso horário era o seguinte:
import pytz
import datetime
timezone = pytz.timezone('America/Sao_Paulo')
aware_date = datetime.datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone)
# datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<DstTzInfo 'America/Sao_Paulo' LMT-1 day, 20\:54\:00 STD>)
Obs: Datas com fuso horário são chamadas de aware.
Esse objeto criado me parecia estranho. O que é esse valor 20:54:00? Por que não há um lindo número redondo? Mas não me preocupei.
Mas algo estranho aconteceu… Quando convertemos essa data para UTC obtemos o seguinte:
aware_date.astimezone(pytz.utc)
# datetime.datetime(2020, 1, 1, 3, 6, tzinfo=<UTC>)
A data/hora em UTC deveria ser 2020-01-01 03:00:00, mas apareceu uma defasagem de 6 minutos. Por que isto?
Isto é uma bizarrice da forma como pytz organiza suas timezones. Aqui tem algumas referências que me foram úteis:
Python pytz timezone function returns a timezone that is off by 9 minutes
pytz localize vs datetime replace
O que entendi que acontece é o seguinte. Para uma dada região nomeada por um fuso horário, pytz armazena todo o histórico de todas as variações de fuso que ocorreram nessa região. Existe um valor específico chamado LMT, ou Local Mean Time, que representa o valor mais antigo desse histórico, antes que os fusos horários do mundo tivessem sido padronizados (no século 19).
Quando você chama puramente pytz.timezone('America/Sao_Paulo)
, o módulo pytz
não sabe qual fuso horário você quer, então ele pode escolher arbitrariamente da
lista em função do sistema operacional e talvez versão do pytz
. No meu caso,
estava retornando esse LMT
.
<DstTzInfo 'America/Sao_Paulo' LMT-1 day, 20\:54\:00
De acordo com as perguntas do stackoverflow que listei acima, é errado
localizar datas usando o inicializador do datetime
passando um timezone do
pytz
direto no tzinfo
. Ou seja:
# ERRADO
aware_date = datetime.datetime(2020, 1, 1, 0, 0, 0, tzinfo=pytz.timezone('America/Sao_Paulo'))
Isto é um problema com o módulo pytz
, e não de datetime
.
O jeito certo é oferecer ao pytz
a informação da data de referência usando o
método localize
:
naive = datetime.datetime(2020, 1, 1, 0, 0, 0)
# CORRETO
aware = pytz.timezone('America/Sao_Paulo').localize(naive)
# datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<DstTzInfo 'America/Sao_Paulo' -03-1 day, 21\:00\:00 STD>)