Fusos horários em Go: como não perder hora, dia ou dados

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que tempo não é só tique-taque

\n\n

Se você já escreveu uma aplicação em Go que lida com datas e usuários de diferentes países, certamente já enfrentou a dor de cabeça chamada "fusos horários". Parece simples, né? É só usar time.Now() e pronto. Até o momento em que seu servidor está em UTC e o cliente em UTC-3, e seu "hoje" magicamente se transforma em "ontem".

\n\n

Neste artigo, vamos explorar como trabalhar corretamente com fusos horários em Go, com exemplos reais, sem enrolação e com blocos de código prontos. Você vai aprender a não perder dados, não enlouquecer na depuração e tornar sua aplicação resiliente a diferenças de fuso horário. Vamos nessa!

\n\n

Fundamentos: o que está por trás do time.Time

\n\n

Em Go, o tipo time.Time não é apenas um número de milissegundos. É uma estrutura que armazena dois componentes principais:

\n\n
    \n
  • Tempo absoluto — a quantidade de nanossegundos desde 1º de janeiro de 1970 (timestamp Unix).
  • \n
  • Location — um ponteiro para o fuso horário.
  • \n
\n\n

É o Location que determina como o tempo será exibido: como 14:00 BRT ou como 17:00 UTC. O erro mais comum de iniciantes é achar que time.Now() retorna a "hora local". Na verdade, ele retorna a hora atual com o fuso horário local do servidor. Mude o código para outro servidor e tudo "desanda".

\n\n

Como criar um horário com um fuso específico

\n\n

A maneira mais simples é usar time.LoadLocation. Ele carrega o fuso horário do banco de dados IANA do sistema (geralmente em /usr/share/zoneinfo).

\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc main() {\n    loc, err := time.LoadLocation("America/Sao_Paulo")\n    if err != nil {\n        panic(err)\n    }\n\n    // Criando um horário em um fuso específico\n    t := time.Date(2025, 3, 15, 14, 30, 0, 0, loc)\n    fmt.Println(t) // 2025-03-15 14:30:00 -0300 -03\n}\n
\n\n

Observe: time.Date recebe o Location como último argumento. Se você passar time.UTC ou time.Local, o comportamento será diferente.

\n\n

E se o fuso horário não estiver no sistema?

\n\n

Às vezes (por exemplo, em imagens Docker mínimas), o banco de dados zoneinfo está ausente. A solução é usar time.FixedZone para criar um fuso com deslocamento fixo:

\n\n
loc := time.FixedZone("BRT", -3*60*60) // -3 horas do UTC\nt := time.Now().In(loc)\nfmt.Println(t)\n
\n\n

Desvantagem: FixedZone não considera horário de verão nem mudanças históricas. Para a maioria das aplicações modernas, isso é aceitável, mas se você precisar de precisão histórica (por exemplo, para operações financeiras de 2014), use LoadLocation.

\n\n

A regra de ouro do desenvolvimento backend: armazene em UTC

\n\n

A regra de ouro ao trabalhar com fusos horários em Go (e em qualquer outra linguagem) é sempre armazenar o horário em UTC e converter apenas na camada de exibição. Por quê?

\n\n
    \n
  • O servidor pode mudar de fuso horário.
  • \n
  • Os usuários podem estar em fusos diferentes.
  • \n
  • Comparar e ordenar horários UTC é trivial.
  • \n
  • Logs são mais fáceis de analisar.
  • \n
\n\n

Veja como fica na prática:

\n\n
// Obtendo o horário do usuário (por exemplo, de um formulário)\nuserTime := "2025-03-15 14:30"\nuserLoc, _ := time.LoadLocation("Asia/Tokyo")\n\n// Interpretando como horário local do usuário\nt, _ := time.ParseInLocation("2006-01-02 15:04", userTime, userLoc)\n\n// Convertendo para UTC antes de salvar\nutcTime := t.UTC()\nfmt.Println(utcTime) // 2025-03-15 05:30:00 +0000 UTC\n
\n\n

Erro comum: perder o Location ao serializar

\n\n

Ao salvar time.Time em um banco de dados (por exemplo, PostgreSQL via database/sql), o driver pode "resetar" o Location para UTC ou Local. Para evitar surpresas, sempre converta

Blogs

Book Recommendations