Tutorial
Trabalhando com fusos horários em Python: guia completo
Introdução
O tratamento de fuso horário em Python pode ser complicado, mas é essencial para construir aplicativos robustos que funcionem em diferentes fusos horários. Este tutorial cobre tudo o que você precisa saber sobre como trabalhar com fusos horários em Python, desde conceitos básicos até técnicas avançadas.
Por que o tratamento do fuso horário é importante
# ❌ BAD: Naive datetime (no timezone info)
from datetime import datetime
now = datetime.now() # Which timezone is this?
# ✅ GOOD: Timezone-aware datetime
from datetime import datetime, timezone
now = datetime.now(timezone.utc) # Clear: UTC time
Problemas comuns com datas ingênuas:
- Tempos ambíguos durante as transições do horário de verão
- Cálculos incorretos entre fusos horários
- Corrupção de dados em sistemas distribuídos
- Bugs de fuso horário difíceis de depurar
Visão geral das bibliotecas de fuso horário Python
1. data e hora (integrado)
Python 3.2+ inclui suporte básico de fuso horário:
from datetime import datetime, timezone, timedelta
# UTC timezone
utc_now = datetime.now(timezone.utc)
print(utc_now) # 2025-01-15 10:30:00+00:00
# Fixed offset timezone
est = timezone(timedelta(hours=-5))
est_now = datetime.now(est)
print(est_now) # 2025-01-15 05:30:00-05:00
Prós:
- Integrado, sem necessidade de instalação
- Simples para UTC e deslocamentos fixos
Contras:
- Não há suporte para fuso horário nomeado (por exemplo, "América/Nova_Iorque")
- Não é possível lidar com o horário de verão automaticamente
- Funcionalidade limitada
2. zoneinfo (integrado, Python 3.9+)
Recomendado para Python 3.9+
from datetime import datetime
from zoneinfo import ZoneInfo
# Named timezone support
ny_time = datetime.now(ZoneInfo("America/New_York"))
tokyo_time = datetime.now(ZoneInfo("Asia/Tokyo"))
print(f"New York: {ny_time}")
print(f"Tokyo: {tokyo_time}")
Prós:
- Integrado (Python 3.9+)
- Suporte ao banco de dados de fuso horário da IANA
- Tratamento automático de horário de verão
- Tipo seguro e moderno
Contras:
- Disponível apenas em Python 3.9+
- Requer dados de fuso horário do sistema (ou pacote
tzdata)
3. pytz (terceiros)
Melhor para Python <3.9 ou compatibilidade máxima
import pytz
from datetime import datetime
# Create timezone-aware datetime
utc = pytz.UTC
eastern = pytz.timezone('US/Eastern')
# Current time in timezone
ny_time = datetime.now(eastern)
print(ny_time)
Prós:
- Funciona em Python 2.7+
- Banco de dados abrangente de fuso horário
- Bem testado e estável
Contras:
- Requer instalação (
pip install pytz) - API um pouco mais complexa
- Sendo substituído por zoneinfo
4. dateutil (terceiros)
from dateutil import tz
from datetime import datetime
# Get timezone
eastern = tz.gettz('America/New_York')
utc = tz.UTC
# Create datetime
dt = datetime.now(eastern)
Prós:
- Análise de data poderosa
- Fácil conversão de fuso horário
- Funciona com fuso horário local
Contras:
- Maior dependência
- Exagero para trabalho simples de fuso horário
Criando datas com reconhecimento de fuso horário
Usando zoneinfo (Python 3.9+)
from datetime import datetime
from zoneinfo import ZoneInfo
# Method 1: Create with timezone
dt = datetime(2025, 1, 15, 14, 30, tzinfo=ZoneInfo("America/New_York"))
# Method 2: Get current time with timezone
now = datetime.now(ZoneInfo("America/New_York"))
# Method 3: Replace timezone on naive datetime
naive_dt = datetime(2025, 1, 15, 14, 30)
aware_dt = naive_dt.replace(tzinfo=ZoneInfo("America/New_York"))
Usando pytz
import pytz
from datetime import datetime
# Method 1: Use localize() for naive datetimes
eastern = pytz.timezone('US/Eastern')
naive_dt = datetime(2025, 1, 15, 14, 30)
aware_dt = eastern.localize(naive_dt)
# Method 2: Get current time (use UTC, then convert)
utc_now = datetime.now(pytz.UTC)
eastern_now = utc_now.astimezone(eastern)
# ❌ WRONG with pytz!
wrong_dt = datetime(2025, 1, 15, 14, 30, tzinfo=eastern)
# This bypasses DST handling!
** Importante pegadinha do pytz: ** Sempre use localize() em vez de passar tzinfo diretamente!
Convertendo entre fusos horários
Conversão Básica
from datetime import datetime
from zoneinfo import ZoneInfo
# Create datetime in one timezone
ny_time = datetime(2025, 1, 15, 14, 30, tzinfo=ZoneInfo("America/New_York"))
# Convert to another timezone
tokyo_time = ny_time.astimezone(ZoneInfo("Asia/Tokyo"))
london_time = ny_time.astimezone(ZoneInfo("Europe/London"))
utc_time = ny_time.astimezone(ZoneInfo("UTC"))
print(f"New York: {ny_time}") # 2025-01-15 14:30:00-05:00
print(f"Tokyo: {tokyo_time}") # 2025-01-16 04:30:00+09:00
print(f"London: {london_time}") # 2025-01-15 19:30:00+00:00
print(f"UTC: {utc_time}") # 2025-01-15 19:30:00+00:00
Função auxiliar de conversão
from datetime import datetime
from zoneinfo import ZoneInfo
def convert_timezone(dt, from_tz, to_tz):
"""
Convert datetime between timezones
Args:
dt: datetime object (naive or aware)
from_tz: Source timezone string (e.g., 'America/New_York')
to_tz: Target timezone string (e.g., 'Asia/Tokyo')
Returns:
Timezone-aware datetime in target timezone
"""
# If datetime is naive, localize it first
if dt.tzinfo is None:
dt = dt.replace(tzinfo=ZoneInfo(from_tz))
# Convert to target timezone
return dt.astimezone(ZoneInfo(to_tz))
# Usage
naive_dt = datetime(2025, 6, 15, 14, 30)
tokyo_time = convert_timezone(naive_dt, "America/New_York", "Asia/Tokyo")
print(tokyo_time) # 2025-06-16 03:30:00+09:00
Tratamento de transições de horário de verão
O horário de verão (DST) cria horários ambíguos e inexistentes.
Tempos ambíguos (retrocesso)
Quando os relógios "retrocedem" (termina o horário de verão), a hora de um relógio ocorre duas vezes:
from datetime import datetime
from zoneinfo import ZoneInfo
import pytz
# November 5, 2023, 01:30 AM happens TWICE in US/Eastern
# Once in EDT (UTC-4), once in EST (UTC-5)
# With zoneinfo (Python 3.9+)
tz = ZoneInfo("America/New_York")
# Create the ambiguous time
dt = datetime(2023, 11, 5, 1, 30, tzinfo=tz)
print(dt) # Uses the first occurrence (DST)
# With pytz - explicit control
eastern = pytz.timezone('US/Eastern')
# First occurrence (DST, UTC-4)
dt_dst = eastern.localize(datetime(2023, 11, 5, 1, 30), is_dst=True)
print(f"DST: {dt_dst}") # 2023-11-05 01:30:00-04:00
# Second occurrence (Standard, UTC-5)
dt_std = eastern.localize(datetime(2023, 11, 5, 1, 30), is_dst=False)
print(f"Standard: {dt_std}") # 2023-11-05 01:30:00-05:00
Tempos Inexistentes (Primavera em Frente)
Quando os relógios "avançam" (o horário de verão começa), alguns horários não existem:
from datetime import datetime
from zoneinfo import ZoneInfo
import pytz
# March 10, 2024, 02:30 AM doesn't exist in US/Eastern
# Clocks jump from 02:00 AM to 03:00 AM
# With zoneinfo - automatically adjusts forward
tz = ZoneInfo("America/New_York")
dt = datetime(2024, 3, 10, 2, 30, tzinfo=tz)
print(dt) # Automatically becomes 03:30
# With pytz - raises error by default
eastern = pytz.timezone('US/Eastern')
try:
dt = eastern.localize(datetime(2024, 3, 10, 2, 30))
except pytz.exceptions.NonExistentTimeError:
print("This time doesn't exist!")
# Handle non-existent time explicitly
dt = eastern.localize(datetime(2024, 3, 10, 2, 30), is_dst=None)
# Returns the next valid time
Melhores práticas
1. Sempre armazene carimbos de data e hora em UTC
from datetime import datetime, timezone
# ✅ GOOD: Store in UTC
def save_event(event_time):
utc_time = event_time.astimezone(timezone.utc)
database.save(utc_time)
return utc_time
# ✅ GOOD: Display in user's timezone
def display_event(utc_time, user_timezone):
local_time = utc_time.astimezone(ZoneInfo(user_timezone))
return local_time.strftime("%Y-%m-%d %H:%M:%S %Z")
2. Use o formato ISO 8601 para serialização
from datetime import datetime
from zoneinfo import ZoneInfo
dt = datetime.now(ZoneInfo("America/New_York"))
# ✅ GOOD: ISO 8601 with timezone
iso_string = dt.isoformat()
print(iso_string) # 2025-01-15T14:30:00-05:00
# Parse back
parsed_dt = datetime.fromisoformat(iso_string)
3. Nunca use datas ingênuas na produção
# ❌ BAD: Naive datetime
naive = datetime.now()
# ✅ GOOD: Always timezone-aware
aware = datetime.now(timezone.utc)
4. Use UTC para cálculos
from datetime import datetime, timedelta, timezone
# ✅ GOOD: Calculate in UTC
start_utc = datetime.now(timezone.utc)
end_utc = start_utc + timedelta(hours=24)
# Then convert to local timezone for display
local_end = end_utc.astimezone(ZoneInfo("America/New_York"))
Erros e soluções comuns
Erro 1: Aritmética com datas ingênuas e conscientes
# ❌ ERROR: Can't mix naive and aware
naive = datetime.now()
aware = datetime.now(timezone.utc)
# difference = aware - naive # TypeError!
# ✅ SOLUTION: Make both timezone-aware
naive_aware = naive.replace(tzinfo=timezone.utc)
difference = aware - naive_aware
Erro 2: uso incorreto de pytz
import pytz
# ❌ WRONG
eastern = pytz.timezone('US/Eastern')
dt = datetime(2025, 1, 15, 14, 30, tzinfo=eastern)
# ✅ CORRECT
dt = eastern.localize(datetime(2025, 1, 15, 14, 30))
Erro 3: assumindo fuso horário local
# ❌ BAD: Assumes server timezone
dt = datetime.now() # Which timezone?
# ✅ GOOD: Explicit timezone
dt = datetime.now(timezone.utc)
Exemplo Prático: Agendador de Reuniões
from datetime import datetime
from zoneinfo import ZoneInfo
class MeetingScheduler:
"""Schedule meetings across timezones"""
def __init__(self):
self.meetings = []
def schedule_meeting(self, date_str, time_str, timezone_str, duration_hours):
"""
Schedule a meeting in a specific timezone
Args:
date_str: Date as 'YYYY-MM-DD'
time_str: Time as 'HH:MM'
timezone_str: IANA timezone (e.g., 'America/New_York')
duration_hours: Meeting duration in hours
"""
# Parse date and time
year, month, day = map(int, date_str.split('-'))
hour, minute = map(int, time_str.split(':'))
# Create timezone-aware datetime
tz = ZoneInfo(timezone_str)
meeting_time = datetime(year, month, day, hour, minute, tzinfo=tz)
# Convert to UTC for storage
meeting_utc = meeting_time.astimezone(ZoneInfo("UTC"))
meeting = {
'start_utc': meeting_utc,
'timezone': timezone_str,
'duration': duration_hours
}
self.meetings.append(meeting)
return meeting
def get_meeting_time(self, meeting, display_timezone):
"""Get meeting time in any timezone"""
tz = ZoneInfo(display_timezone)
local_time = meeting['start_utc'].astimezone(tz)
return {
'time': local_time.strftime("%Y-%m-%d %H:%M %Z"),
'timezone': display_timezone
}
# Usage example
scheduler = MeetingScheduler()
# Schedule meeting in New York
meeting = scheduler.schedule_meeting(
'2025-02-15', '14:00', 'America/New_York', 1
)
# Display for different participants
print("Meeting times:")
print(f" New York: {scheduler.get_meeting_time(meeting, 'America/New_York')['time']}")
print(f" London: {scheduler.get_meeting_time(meeting, 'Europe/London')['time']}")
print(f" Tokyo: {scheduler.get_meeting_time(meeting, 'Asia/Tokyo')['time']}")
Saída:
Meeting times:
New York: 2025-02-15 14:00 EST
London: 2025-02-15 19:00 GMT
Tokyo: 2025-02-16 04:00 JST
Testando código de fuso horário
import unittest
from datetime import datetime
from zoneinfo import ZoneInfo
class TestTimezoneConversion(unittest.TestCase):
def test_utc_to_eastern(self):
"""Test UTC to Eastern conversion"""
utc_time = datetime(2025, 1, 15, 19, 30, tzinfo=ZoneInfo("UTC"))
eastern_time = utc_time.astimezone(ZoneInfo("America/New_York"))
# In January, Eastern is UTC-5 (EST)
self.assertEqual(eastern_time.hour, 14)
self.assertEqual(eastern_time.minute, 30)
def test_dst_transition(self):
"""Test DST transition handling"""
# Before DST (March 10, 2024, 1:00 AM)
before_dst = datetime(2024, 3, 10, 1, 0, tzinfo=ZoneInfo("America/New_York"))
# After DST (March 10, 2024, 3:00 AM - 2:00 AM doesn't exist)
after_dst = datetime(2024, 3, 10, 3, 0, tzinfo=ZoneInfo("America/New_York"))
# Difference should be 1 hour in local time, 2 hours in absolute time
diff = after_dst - before_dst
self.assertEqual(diff.total_seconds(), 3600) # 1 hour
if __name__ == '__main__':
unittest.main()
Ferramentas e recursos relacionados
Use nossas ferramentas gratuitas de carimbo de data/hora para trabalhar com fusos horários:
- Conversor UTC/Local - Converte entre UTC e hora local
- Planejador de reunião de fuso horário - Agendamento entre fusos horários
- Current Timestamp - Obtenha a hora atual em vários fusos horários
- Exemplos de carimbo de data/hora em Python - Mais exemplos de código Python
Resumo
Principais conclusões:
- Sempre use datas com reconhecimento de fuso horário no código de produção
- Armazene carimbos de data/hora em UTC, converta para local para exibição
- Use zoneinfo (Python 3.9+) ou pytz para versões mais antigas
- Trate transições de horário de verão explicitamente quando necessário
- Teste o código do fuso horário minuciosamente, especialmente perto do horário de verão
- Nunca assuma o fuso horário local - seja sempre explícito
Referência rápida:
# Modern Python (3.9+)
from datetime import datetime
from zoneinfo import ZoneInfo
# Current UTC time
utc_now = datetime.now(ZoneInfo("UTC"))
# Current local time
local_now = datetime.now(ZoneInfo("America/New_York"))
# Convert between timezones
tokyo_time = local_now.astimezone(ZoneInfo("Asia/Tokyo"))
# ISO 8601 format (with timezone)
iso_string = tokyo_time.isoformat()
Com essas técnicas, você será capaz de lidar com fusos horários com confiança em seus aplicativos Python!
Última atualização: janeiro de 2025