Datas e horas em Python com datetime e zoneinfo — 2026
Aprenda a trabalhar com datas e horas em Python: criar, formatar, comparar e somar períodos com datetime e timedelta, e lidar com fuso horário brasileiro usando zoneinfo. Guia prático em português.
Trabalhar com datas e horas é uma das tarefas mais comuns em qualquer programa Python: registrar quando um pedido foi feito, calcular a validade de um produto, agendar o envio de um relatório, filtrar vendas do último mês ou exibir um carimbo de data/hora no fuso de Brasília. Apesar de universal, é também uma das fontes mais frequentes de bugs — geralmente por causa de fuso horário, formato ou de tentar misturar datas “ingênuas” com datas “conscientes”.
O Python resolve boa parte disso na própria biblioteca padrão, com os módulos datetime e, a partir da versão 3.9, zoneinfo. Neste tutorial você vai aprender a criar, formatar, comparar e somar períodos, e — o mais importante para quem desenvolve para o mercado brasileiro — a lidar corretamente com o fuso horário de Brasília. Se você está começando agora, vale revisar antes o nosso guia de Python para iniciantes; aqui o foco é um tópico específico, mas fundamental, da linguagem.
Os objetos básicos: date, time e datetime
O módulo datetime oferece três tipos principais. O date representa apenas uma data (dia, mês, ano); o time representa apenas um horário (hora, minuto, segundo, microssegundo); e o datetime junta os dois. Para a maioria das aplicações do dia a dia, o datetime é o que você vai usar:
from datetime import date, time, datetime
hoje = date.today() # 2026-07-05
agora = datetime.now() # 2026-07-05 14:30:00.123456
horario = time(9, 0, 0) # 09:00:00
print(hoje, agora, horario)
Repare que date.today() devolve só a data, enquanto datetime.now() traz data e hora. Escolha o tipo certo para o seu problema: guardar a data de nascimento de um cliente pede um date; registrar o instante exato de uma transação pede um datetime.
Criando datas e horas manualmente
Você pode construir datas informando ano, mês e dia diretamente. É assim que você converte um valor vindo de um formulário ou de um banco de dados:
from datetime import date, datetime
aniversario = date(2026, 12, 25)
pagamento = datetime(2026, 7, 5, 10, 0, 0) # 5 de julho de 2026, 10h00
# A partir de uma string ISO 8601 (formato recomendado para troca de dados)
data_iso = date.fromisoformat("2026-07-05")
dt_iso = datetime.fromisoformat("2026-07-05T10:00:00")
# A partir de um timestamp Unix (segundos desde 01/01/1970)
import time as _time
ts = _time.time()
data_ts = datetime.fromtimestamp(ts)
O formato ISO 8601 (AAAA-MM-DD) é o padrão internacional e o mais seguro para gravar e trocar datas entre sistemas. Guarde sempre assim no banco de dados; deixe a formatação brasileira (dd/mm/aaaa) apenas para a tela.
Formatando datas no padrão brasileiro
Para mostrar uma data ao usuário, use o método strftime (string format time) com os códigos de formatação. O padrão brasileiro usa dia, mês e ano com barras:
from datetime import datetime
agora = datetime.now()
print(agora.strftime("%d/%m/%Y")) # 05/07/2026
print(agora.strftime("%d/%m/%Y %H:%M")) # 05/07/2026 14:30
print(agora.strftime("%d de %B de %Y")) # 05 de July de 2026 (problema!)
Atenção ao último exemplo: por padrão, o nome do mês sai em inglês (July). Para ter 05 de julho de 2026 você precisa definir o locale do sistema, o que é frágil entre sistemas operacionais. A abordagem mais robusta, e a que recomendamos para sites brasileiros, é montar o texto à mão usando uma lista de meses em português:
meses = ["janeiro", "fevereiro", "março", "abril", "maio", "junho",
"julho", "agosto", "setembro", "outubro", "novembro", "dezembro"]
texto = f"{agora.day} de {meses[agora.month - 1]} de {agora.year}"
print(texto) # 5 de julho de 2026
Assim o resultado não depende do sistema operacional nem de variáveis de ambiente — útil em containers Docker, onde o locale quase nunca está configurado.
Convertendo strings em datas com strptime
O caminho inverso — transformar uma string em data — usa strptime (string parse time). É essencial ao ler planilhas, arquivos CSV ou respostas de APIs em que a data vem como texto:
from datetime import datetime
texto = "05/07/2026 14:30"
data = datetime.strptime(texto, "%d/%m/%Y %H:%M")
print(data) # 2026-07-05 14:30:00
# Apenas data
data_pura = datetime.strptime("25/12/2026", "%d/%m/%Y").date()
Os códigos precisam casar exatamente com o formato da string, incluindo barras, espaços e dois-pontos. Se a string não bater, o Python levanta ValueError — por isso envolva essa conversão em um bloco try/except, conforme mostramos no nosso guia de tratamento de erros em Python.
Operações com datas usando timedelta
Para somar ou subtrair períodos, use timedelta, que representa uma duração (dias, segundos, microssegundos). Subtrair duas datas também devolve um timedelta:
from datetime import date, timedelta
hoje = date.today()
vencimento = date(2026, 7, 20)
dias_restantes = vencimento - hoje # timedelta
print(dias_restantes.days) # número de dias até o vencimento
# Adicionar e remover períodos
daqui_uma_semana = hoje + timedelta(weeks=1)
ha_30_dias = hoje - timedelta(days=30)
print(daqui_uma_semana, ha_30_dias)
timedelta cobre a maioria dos cálculos de prazo. Para operações mais complexas — como “o mesmo dia do mês que vem” ou “o último dia útil” — a biblioteca de terceiros python-dateutil oferece o relativedelta, que entende meses e anos (unidades que o timedelta não suporta diretamente).
Fuso horário com zoneinfo
Até aqui, todas as nossas datas eram ingênuas (naive): não carregam informação de fuso horário. Em aplicações que rodam em servidores na nuvem, recebem webhooks internacionais ou atendem usuários em fusos diferentes, datas ingênuas causam bugs difíceis de rastrear. A partir do Python 3.9, o módulo zoneinfo resolve isso de forma nativa:
from datetime import datetime
from zoneinfo import ZoneInfo
# Horário atual no fuso de Brasília (UTC-3)
agora_sp = datetime.now(ZoneInfo("America/Sao_Paulo"))
print(agora_sp) # 2026-07-05 14:30:00-03:00
# O sufixo -03:00 indica que a data é "consciente" (aware)
# Converter entre fusos é direto:
agora_noronha = agora_sp.astimezone(ZoneInfo("America/Noronha"))
print(agora_noronha) # 2026-07-05 14:30:00-02:00 (UTC-2)
A regra de ouro do fuso brasileiro: o Brasil usa vários fusos, mas o principal é o de Brasília, representado por America/Sao_Paulo (UTC-3). Outros comuns são America/Manaus (UTC-4), America/Rio_Branco (UTC-5, o Acre) e America/Noronha (UTC-2). O horário de verão brasileiro foi extinto em 2019, então o país mantém o horário padrão o ano inteiro — a base de dados de fusos já reflete isso, e você não precisa se preocupar com ajustes sazonais.
Dica de produção: em Windows, instale o pacote
tzdata(pip install tzdata) para garantir regras de fuso atualizadas, já que o sistema não fornece a base tz por padrão. Em Linux e macOS, as regras vêm do próprio sistema operacional.
O erro mais comum: misturar datas ingênuas e conscientes
O bug clássico de datas em Python acontece quando você compara ou subtrai uma data com fuso e outra sem:
from datetime import datetime
from zoneinfo import ZoneInfo
ingenua = datetime.now() # sem fuso
consciente = datetime.now(ZoneInfo("America/Sao_Paulo")) # com fuso
# A linha abaixo levanta TypeError: can't compare offset-naive
# and offset-aware datetimes
# if ingenua > consciente:
# print("maior")
A correção é padronizar todo o código em um dos dois modos. A recomendação moderna é trabalhar sempre com datas conscientes em código de produção, criando-as com datetime.now(ZoneInfo("America/Sao_Paulo")). Se precisar converter uma data ingênua para consciente, use .replace(tzinfo=...) ou .astimezone():
# Localizar uma data ingênua assumindo o fuso de Brasília
ingenua_localizada = ingenua.replace(tzinfo=ZoneInfo("America/Sao_Paulo"))
print(ingenua_localizada > consciente) # agora funciona
Mini-projeto: calculando dias até o vencimento de uma conta
Vamos juntar tudo em um exemplo realista — uma função que recebe a data de vencimento de uma conta e devolve quantos dias faltam, com aviso de atraso:
from datetime import date
def status_vencimento(vencimento: date) -> str:
hoje = date.today()
diff = (vencimento - hoje).days
if diff > 0:
return f"Faltam {diff} dias para o vencimento."
elif diff == 0:
return "O vencimento é hoje."
else:
return f"Conta vencida há {abs(diff)} dias."
print(status_vencimento(date(2026, 7, 20))) # "Faltam N dias..."
print(status_vencimento(date(2026, 6, 30))) # "Conta vencida há N dias..."
Repare em três decisões importantes: usei date (e não datetime) porque o vencimento não tem hora; subtrair datas já devolve um timedelta, do qual extraio .days; e usei abs() para inverter o sinal na mensagem de atraso. Esse é o tipo de lógica que aparece em automações com Python para finanças, relatórios e cobranças.
Próximos passos
Para evoluir depois deste tutorial:
- Automatize com datas — combine
datetimecom agendamento para gerar relatórios diários ou enviar lembretes de vencimento. - Valide entradas — use
try/exceptem volta destrptimee gere datas compydanticquando os dados vêm de APIs; veja nosso guia de validação de dados com Pydantic. - Teste fusos — escreva testes que cobrem a conversão entre
America/Sao_Pauloe UTC, seguindo as práticas do nosso guia de testes unitários em Python.
Datas e horas parecem triviais até a primeira vez que um fuso horário quebra uma cobrança no fim do mês. Trabalhe sempre com datas conscientes, guarde tudo em ISO 8601 e formate para o padrão brasileiro apenas na hora de exibir. Com esses três hábitos, a maioria dos bugs de data desaparece. Para acompanhar as oportunidades do mercado de desenvolvimento Python no Brasil, confira nossas vagas de Python e o guia de primeiro emprego como programador Python.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português