Datas e horas estão em todo lugar no desenvolvimento de software — registros de log, validade de tokens, agendamentos, relatórios por período, cálculo de prazos. Python oferece um conjunto robusto de ferramentas para manipular tempo, mas o tema tem armadilhas importantes: fusos horários, horário de verão e formatos de string são fontes clássicas de bugs sutis em sistemas reais.
O Módulo datetime
O módulo principal para trabalhar com datas e horas:
from datetime import date, time, datetime, timedelta
# date — apenas data
hoje = date.today()
print(hoje) # 2024-03-15
print(hoje.year) # 2024
print(hoje.month) # 3
print(hoje.day) # 15
# time — apenas hora
agora_hora = time(14, 30, 45)
print(agora_hora) # 14:30:45
print(agora_hora.hour) # 14
# datetime — data e hora combinadas
agora = datetime.now()
print(agora) # 2024-03-15 14:30:45.123456
# Criando datetime específico
aniversario = datetime(1989, 7, 4, 8, 0, 0)
print(aniversario) # 1989-07-04 08:00:00
# Data de hoje como datetime
hoje_dt = datetime.today()
print(hoje_dt.date()) # 2024-03-15
print(hoje_dt.time()) # 14:30:45.123456
timedelta: Aritmética com Datas
timedelta representa uma duração — a diferença entre dois instantes:
from datetime import datetime, timedelta
agora = datetime.now()
# Somando e subtraindo
amanha = agora + timedelta(days=1)
semana_passada = agora - timedelta(weeks=1)
daqui_2_horas = agora + timedelta(hours=2)
daqui_90_min = agora + timedelta(minutes=90)
print(amanha)
print(semana_passada)
# Diferença entre datas
nascimento = datetime(1990, 5, 20)
hoje = datetime.today()
diferenca = hoje - nascimento
print(f"Dias vividos: {diferenca.days}")
print(f"Anos aproximados: {diferenca.days // 365}")
# timedelta tem: days, seconds, microseconds
delta = timedelta(days=2, hours=3, minutes=30)
print(delta.total_seconds()) # 95400.0
Formatação: strftime e strptime
Converter entre objetos datetime e strings:
from datetime import datetime
agora = datetime.now()
# strftime — datetime → string (format time)
print(agora.strftime("%d/%m/%Y")) # 15/03/2024
print(agora.strftime("%d/%m/%Y %H:%M")) # 15/03/2024 14:30
print(agora.strftime("%Y-%m-%dT%H:%M:%S")) # 2024-03-15T14:30:45
print(agora.strftime("%A, %d de %B de %Y")) # Friday, 15 de March de 2024
# strptime — string → datetime (parse time)
texto = "25/12/2024 08:00"
natal = datetime.strptime(texto, "%d/%m/%Y %H:%M")
print(natal)
print(type(natal)) # <class 'datetime.datetime'>
# ISO 8601 — formato padrão para APIs
iso = agora.isoformat()
print(iso) # 2024-03-15T14:30:45.123456
# Parsing de ISO 8601 (Python 3.7+)
dt = datetime.fromisoformat("2024-03-15T14:30:45")
print(dt)
Principais códigos de formato:
%Y ano com 4 dígitos 2024
%m mês com zero à esquerda 03
%d dia com zero à esquerda 15
%H hora 24h 14
%M minutos 30
%S segundos 45
%A dia da semana Friday
%B nome do mês March
%p AM ou PM PM
%f microssegundos 123456
Fusos Horários com zoneinfo
Datas sem fuso horário são chamadas de naive — funcionam bem para uso local, mas são problemáticas em sistemas distribuídos. Datas com fuso horário são chamadas de aware.
from datetime import datetime
from zoneinfo import ZoneInfo # Python 3.9+
# datetime naive — sem fuso horário
naive = datetime.now()
print(naive.tzinfo) # None
# datetime aware — com fuso horário
fuso_sp = ZoneInfo("America/Sao_Paulo")
fuso_utc = ZoneInfo("UTC")
fuso_ny = ZoneInfo("America/New_York")
agora_sp = datetime.now(tz=fuso_sp)
agora_utc = datetime.now(tz=fuso_utc)
print(agora_sp) # 2024-03-15 14:30:45-03:00
print(agora_utc) # 2024-03-15 17:30:45+00:00
# Convertendo entre fusos
agora_ny = agora_sp.astimezone(fuso_ny)
print(agora_ny) # 2024-03-15 14:30:45-04:00
# Regra prática: armazene sempre em UTC, exiba no fuso do usuário
def agora_utc_iso():
return datetime.now(tz=ZoneInfo("UTC")).isoformat()
print(agora_utc_iso()) # 2024-03-15T17:30:45.123456+00:00
O Módulo calendar
import calendar
# Calendário de um mês
print(calendar.month(2024, 12))
# Verificações
print(calendar.isleap(2024)) # True — 2024 é bissexto
print(calendar.isleap(2023)) # False
# Dias no mês
print(calendar.monthrange(2024, 2)) # (3, 29) — começa na 4ª feira, 29 dias
# Dia da semana (0=segunda, 6=domingo)
print(calendar.weekday(2024, 7, 4)) # 3 = quinta-feira
Medição de Tempo com time e perf_counter
import time
# time.time() — timestamp Unix (segundos desde 1970-01-01)
inicio = time.time()
time.sleep(0.1) # pausa de 100ms
fim = time.time()
print(f"Decorrido: {fim - inicio:.3f}s")
# perf_counter — maior precisão para benchmarks
inicio = time.perf_counter()
resultado = sum(i ** 2 for i in range(1_000_000))
fim = time.perf_counter()
print(f"Processamento: {fim - inicio:.4f}s")
# time.sleep() — pausa a execução
print("Aguardando...")
time.sleep(2)
print("Retomando.")
# Timestamp atual
print(time.time()) # 1710512245.123
print(int(time.time())) # 1710512245
dateutil: Parsing Flexível
Para cenários onde o formato da data é incerto ou variado, a biblioteca python-dateutil é indispensável:
pip install python-dateutil
from dateutil.parser import parse
from dateutil.relativedelta import relativedelta
# Parsing inteligente de qualquer formato
datas = [
"15/03/2024",
"March 15, 2024",
"2024-03-15",
"15 de março de 2024",
"03-15-2024",
"15.03.2024",
]
for texto in datas:
try:
dt = parse(texto, dayfirst=True)
print(f"{texto:30} → {dt.strftime('%d/%m/%Y')}")
except Exception as e:
print(f"{texto:30} → Erro: {e}")
# relativedelta — diferenças em anos, meses e dias
nascimento = datetime(1990, 5, 20)
hoje = datetime.today()
idade = relativedelta(hoje, nascimento)
print(f"Idade: {idade.years} anos, {idade.months} meses, {idade.days} dias")
# Somando meses e anos corretamente
hoje = datetime(2024, 1, 31)
proximo_mes = hoje + relativedelta(months=1)
print(proximo_mes) # 2024-02-29 — ajusta para o último dia do mês
Exemplo Completo: Sistema de Agendamentos
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from dataclasses import dataclass, field
from typing import List
import uuid
FUSO = ZoneInfo("America/Sao_Paulo")
@dataclass
class Compromisso:
titulo: str
inicio: datetime
duracao: timedelta
descricao: str = ""
id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
@property
def fim(self) -> datetime:
return self.inicio + self.duracao
@property
def duracao_minutos(self) -> int:
return int(self.duracao.total_seconds() / 60)
def conflita_com(self, outro: "Compromisso") -> bool:
return self.inicio < outro.fim and self.fim > outro.inicio
def __str__(self):
return (f"[{self.id}] {self.titulo}\n"
f" Início: {self.inicio.strftime('%d/%m/%Y %H:%M')}\n"
f" Fim: {self.fim.strftime('%d/%m/%Y %H:%M')}\n"
f" Duração: {self.duracao_minutos} minutos")
class Agenda:
def __init__(self, nome: str):
self.nome = nome
self._compromissos: List[Compromisso] = []
def agendar(self, compromisso: Compromisso) -> bool:
for existente in self._compromissos:
if compromisso.conflita_com(existente):
print(f"Conflito com: '{existente.titulo}'")
return False
self._compromissos.append(compromisso)
print(f"Agendado: '{compromisso.titulo}'")
return True
def compromissos_do_dia(self, data: datetime) -> List[Compromisso]:
return sorted(
[c for c in self._compromissos
if c.inicio.date() == data.date()],
key=lambda c: c.inicio
)
def proximos(self, n: int = 5) -> List[Compromisso]:
agora = datetime.now(tz=FUSO)
futuros = [c for c in self._compromissos if c.inicio >= agora]
return sorted(futuros, key=lambda c: c.inicio)[:n]
def exibir_dia(self, data: datetime):
compromissos = self.compromissos_do_dia(data)
print(f"\n=== {self.nome} — {data.strftime('%d/%m/%Y')} ===")
if not compromissos:
print(" Nenhum compromisso.")
for c in compromissos:
print(c)
# Uso
agenda = Agenda("Ricardo Matos")
hoje = datetime.now(tz=FUSO).replace(hour=0, minute=0, second=0, microsecond=0)
compromissos = [
Compromisso(
titulo="Reunião de planejamento",
inicio=hoje.replace(hour=9, minute=0),
duracao=timedelta(hours=1),
),
Compromisso(
titulo="Almoço com cliente",
inicio=hoje.replace(hour=12, minute=0),
duracao=timedelta(hours=1, minutes=30),
),
Compromisso(
titulo="Code review",
inicio=hoje.replace(hour=14, minute=0),
duracao=timedelta(minutes=45),
),
Compromisso(
titulo="Conflito — tenta marcar às 9h30",
inicio=hoje.replace(hour=9, minute=30),
duracao=timedelta(hours=1),
),
]
for c in compromissos:
agenda.agendar(c)
agenda.exibir_dia(hoje)
print(f"\n=== Próximos {len(agenda.proximos())} compromissos ===")
for c in agenda.proximos():
faltam = c.inicio - datetime.now(tz=FUSO)
print(f" {c.titulo} — em {faltam.seconds // 3600}h {(faltam.seconds % 3600) // 60}min")
Resumo
date,timeedatetimesão os tipos fundamentais do módulodatetimetimedeltarepresenta durações e permite aritmética entre datasstrftimeconverte datetime → string;strptimeconverte string → datetime- Use sempre ISO 8601 (
isoformat()) para armazenar e transmitir datas em APIs - Datas naive não têm fuso; datas aware têm — prefira aware em sistemas distribuídos
zoneinfo(Python 3.9+) é a forma moderna de trabalhar com fusos horáriospython-dateutilsimplifica parsing de formatos variados e cálculos com meses e anos- Armazene sempre em UTC; converta para o fuso do usuário apenas na exibição
Referências e Leituras Complementares
- Módulo datetime — documentação oficial — https://docs.python.org/3/library/datetime.html
- zoneinfo — fusos horários (PEP 615) — https://docs.python.org/3/library/zoneinfo.html
- python-dateutil — https://dateutil.readthedocs.io/en/stable/
- Banco de dados de fusos horários IANA — https://www.iana.org/time-zones
- PEP 615 — zoneinfo — https://peps.python.org/pep-0615/
- BEAZLEY, David; JONES, Brian K. Python Cookbook. 3. ed. O'Reilly Media, 2013. Cap. 3 — receitas para datas, horas e cálculos temporais.
- AKINSHIN, Andrey. Pro .NET Benchmarking. Apress, 2019. — referência complementar sobre medição precisa de tempo em software.