1. Introdução
Se você acha que timezone é só "somar/subtrair umas horas", logo vai sentir na pele a dor de quase todo programador do planeta. Fuso horário, mudança pra horário de verão/inverno, exceptions do nada quando tenta "converter hora pra outra região" — tudo isso pode virar bug difícil de achar e mais difícil ainda de explicar pro usuário.
O pior: não existe e nunca existiu um "jeitão universal" de lidar com hora — tipo, dois países vizinhos podem tratar horário de verão de jeitos diferentes, e às vezes decidem mudar isso no meio do ano (sim, acontece de verdade!). Exemplo: em 2011, Samoa "pulou" um dia inteiro e trocou de lado na linha internacional de data.
Mas C# e .NET têm ferramentas poderosas pra pelo menos tentar domar esse caos. Hoje vamos conhecer nosso salvador — o tipo TimeZoneInfo e suas funções que vão te ajudar (e ao seu app).
Quando e por que você precisa lidar com timezones
- Quando usuários/servidores estão em regiões diferentes do mundo
- Quando você salva data e hora no banco em UTC, mas mostra pro usuário no horário local dele
- Pra calcular agendas, lembretes, eventos online ("O webinar começa às 19:00 em Berlim — e aí, que horas vai ser pra você?")
- Ao desenvolver sistemas corporativos/globais (tipo contabilidade ou CRM pra empresa internacional)
Visão geral do tipo TimeZoneInfo
TimeZoneInfo é uma classe feita especialmente no .NET pra representar info de fuso horário: o offset em relação ao UTC, nome, suporte a horário de verão e por aí vai.
Diferente da classe antiga TimeZone, que só sabe do timezone "local" e "UTC", TimeZoneInfo te dá acesso completo a todos os timezones registrados no sistema (e até uns custom, criados na mão).
2. Pegando informações de timezones
Timezone local e UTC
Pegar info do timezone local é moleza:
// Local (ou seja, onde seu programa tá rodando agora)
TimeZoneInfo local = TimeZoneInfo.Local;
// Sempre UTC
TimeZoneInfo utc = TimeZoneInfo.Utc;
Console.WriteLine(local.DisplayName); // Tipo: (UTC+01:00) Berlin, Vienna
Console.WriteLine(utc.DisplayName); // (UTC) Coordinated Universal Time
Todos os timezones disponíveis
Às vezes você precisa mostrar pro usuário a lista de todos os timezones possíveis (tipo na hora de cadastrar no serviço):
foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
{
Console.WriteLine($"{tz.Id} | {tz.DisplayName}");
}
Isso vai printar uma lista tipo:
- Central European Standard Time | (UTC+01:00) Berlin, Vienna
- Pacific Standard Time | (UTC-08:00) Pacific Time (US & Canada)
- e por aí vai.
Visualizando: principais propriedades do TimeZoneInfo
| Propriedade | Descrição |
|---|---|
|
Identificador do sistema pro timezone (usado pra buscar) |
|
Descrição pro usuário (com horas e cidades) |
|
Nome do horário "normal" |
|
Nome do horário de verão |
|
Offset em relação ao UTC (tipo, +01:00 pra Berlim) |
|
True se o timezone suporta horário de verão/inverno |
3. Convertendo hora entre timezones
É pra isso tudo que serve! Você precisa:
- Converter hora de uma zona pra outra (tipo, o servidor salvou em UTC, mas mostra pro usuário no timezone dele)
- Lidar direito com horário de verão/inverno
Sintaxe geral
DateTime utcNow = DateTime.UtcNow;
// Por exemplo, vamos converter essa hora pro timezone da Europa Central
TimeZoneInfo europeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
DateTime europeTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, europeZone);
Console.WriteLine($"UTC now: {utcNow}"); // 2024-06-20 10:30:00
Console.WriteLine($"Europe now: {europeTime}"); // 2024-06-20 11:30:00
Importante: Sempre use UTC pra cálculos internos e armazenamento, e só converta pra timezone do usuário na hora de mostrar!
Como descobrir o identificador do timezone?
No Windows, os identificadores (Id) dos timezones são específicos (tipo "Central European Standard Time"), já no Linux/Unix — normalmente são os IANA/Olson ("Europe/Berlin", "America/New_York").
Você pode pegar a lista de identificadores do seu sistema usando TimeZoneInfo.GetSystemTimeZones().
4. Convertendo entre zonas aleatórias
Digamos que você pegou uma hora de Londres (GMT/UTC+0) e quer converter pra Tóquio (UTC+9):
DateTime londonTime = new DateTime(2024, 6, 20, 12, 0, 0, DateTimeKind.Unspecified);
TimeZoneInfo londonZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
TimeZoneInfo tokyoZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
// Primeiro passo: converte pra UTC (se for local)
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(londonTime, londonZone);
// Segundo passo: converte UTC pra hora de Tóquio
DateTime tokyoTime = TimeZoneInfo.ConvertTimeFromUtc(utc, tokyoZone);
Console.WriteLine($"London: {londonTime} | UTC: {utc} | Tokyo: {tokyoTime}");
Lidando com horário de verão e inverno
TimeZoneInfo leva em conta horário de verão/inverno — se o timezone suportar isso.
TimeZoneInfo eastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dateWinter = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Unspecified);
DateTime dateSummer = new DateTime(2024, 7, 1, 12, 0, 0, DateTimeKind.Unspecified);
Console.WriteLine( TimeZoneInfo.ConvertTimeToUtc(dateWinter, eastern)); // Offset UTC-5
Console.WriteLine( TimeZoneInfo.ConvertTimeToUtc(dateSummer, eastern)); // Offset UTC-4 — horário de verão!
Mesmo se os EUA (do exemplo acima) resolverem acabar com o horário de verão (e todo ano rola essa discussão!), a base de timezones do seu SO vai ser atualizada, e seu código vai continuar funcionando certo.
5. Dicas práticas e erros comuns
Exceptions e pegadinhas
- No momento da mudança de horário (tipo, 2:30 da manhã no dia da troca pro horário de verão/inverno), alguns horários podem ser "inexistentes" ou "repetidos":
Por exemplo, no dia de atrasar o relógio, 2:30 acontece duas vezes — e cada uma é diferente. - Lembra: a lista de timezones pode mudar com updates do SO! Exemplo: alguns países cancelam/implementam horário de verão.
GO TO FULL VERSION