Introdução: por que tempo não é só tique-taque
\n\nSe 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".
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\nFundamentos: o que está por trás do time.Time
\n\nEm Go, o tipo time.Time não é apenas um número de milissegundos. É uma estrutura que armazena dois componentes principais:
- \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
É 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".
Como criar um horário com um fuso específico
\n\nA maneira mais simples é usar time.LoadLocation. Ele carrega o fuso horário do banco de dados IANA do sistema (geralmente em /usr/share/zoneinfo).
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\nObserve: time.Date recebe o Location como último argumento. Se você passar time.UTC ou time.Local, o comportamento será diferente.
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:
loc := time.FixedZone("BRT", -3*60*60) // -3 horas do UTC\nt := time.Now().In(loc)\nfmt.Println(t)\n\n\nDesvantagem: 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.
A regra de ouro do desenvolvimento backend: armazene em UTC
\n\nA 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
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\nErro comum: perder o Location ao serializar
\n\nAo 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