Convertendo horários entre fusos horários

Está se tornando cada vez mais importante para qualquer aplicativo que trabalha com datas e horas lidar com diferenças entre fusos horários. Os aplicativos não podem mais presumir que todos os horários podem ser expressos na hora local, que é a hora disponível na estrutura DateTime. Por exemplo, uma página da Web que exibe a hora atual no leste dos Estados Unidos não terá credibilidade para um cliente no leste da Ásia. Este artigo explica como converter horas de um fuso horário para outro, bem como converter valores de DateTimeOffset que têm percepção limitada de fuso horário.

Convertendo para o Tempo Universal Coordenado

O UTC (Tempo Universal Coordenado) é um padrão de tempo atômico de alta precisão. Os fusos horários do mundo são expressos como deslocamentos positivos ou negativos com relação ao UTC. Sendo assim, o UTC fornece uma hora livre de fuso horário ou com fuso horário neutro. O uso do UTC é recomendado quando a portabilidade da data e hora entre computadores é importante. Para detalhes e outras práticas recomendadas usando datas e horas, confira Melhores práticas de codificação usando DateTime no .NET Framework. Converter fusos horários individuais em UTC facilita comparações de hora.

Observação

Você também pode serializar uma estrutura DateTimeOffset para representar sem ambiguidade um único ponto no tempo. Uma vez que objetos DateTimeOffset armazenam um valor de data e hora com seu deslocamento com relação ao UTC, eles sempre representam um ponto específico no tempo em relação ao UTC.

A maneira mais fácil de converter uma hora em UTC é chamar o método TimeZoneInfo.ConvertTimeToUtc(DateTime)static (Shared no Visual Basic). A conversão exata executada pelo método depende do valor da propriedade Kind do parâmetro dateTime, conforme é mostrado na tabela a seguir:

DateTime.Kind Conversão
DateTimeKind.Local Converte a hora local para UTC.
DateTimeKind.Unspecified Presume que o parâmetro dateTime é a hora local e converte a hora local para UTC.
DateTimeKind.Utc Retorna o parâmetro dateTime inalterado.

O código a seguir converte a hora local atual para UTC e exibe o resultado no console:

DateTime dateNow = DateTime.Now;
Console.WriteLine($"The date and time are {TimeZoneInfo.ConvertTimeToUtc(dateNow)} UTC.");
Dim dateNow As Date = Date.Now
Console.WriteLine("The date and time are {0} UTC.", _
                  TimeZoneInfo.ConvertTimeToUtc(dateNow))

Se o valor de data e hora não representar a hora local ou o UTC, o método ToUniversalTime provavelmente retornará um resultado com erro. No entanto, você pode usar o método TimeZoneInfo.ConvertTimeToUtc para converter a data e hora de um fuso horário especificado. Para obter detalhes sobre como recuperar um objeto TimeZoneInfo que representa o fuso horário de destino, confira Encontrando os fusos horários definidos em um sistema local. O código a seguir usa o método TimeZoneInfo.ConvertTimeToUtc para converter a Zona de Tempo Oriental para UTC:

DateTime easternTime = new DateTime(2007, 01, 02, 12, 16, 00);
string easternZoneId = "Eastern Standard Time";
try
{
    TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
    Console.WriteLine($"The date and time are {TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone)} UTC.");
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine($"Unable to find the {easternZoneId} zone in the registry.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine($"Registry data on the {easternZoneId} zone has been corrupted.");
}
Dim easternTime As New Date(2007, 01, 02, 12, 16, 00)
Dim easternZoneId As String = "Eastern Standard Time"
Try
    Dim easternZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId)
    Console.WriteLine("The date and time are {0} UTC.", _
                      TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("Unable to find the {0} zone in the registry.", _
                      easternZoneId)
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the {0} zone has been corrupted.", _
                      easternZoneId)
End Try

O método TimeZoneInfo.ConvertTimeToUtc gera uma ArgumentException se a propriedade Kind do objeto DateTime e o fuso horário não corresponderem. Uma incompatibilidade ocorre se a propriedade Kind é DateTimeKind.Local, mas o objeto TimeZoneInfo não representa o fuso horário local ou se a propriedade Kind é DateTimeKind.Utc, mas o objeto TimeZoneInfo não é igual a TimeZoneInfo.Utc.

Todos esses métodos usam valores de DateTime como parâmetros e retornam um valor de DateTime. Para valores de DateTimeOffset, a estrutura DateTimeOffset tem um método de instância ToUniversalTime que converte a data e hora da instância atual em UTC. O exemplo a seguir chama o método ToUniversalTime para converter uma hora local e várias outras horas para UTC:

DateTimeOffset localTime, otherTime, universalTime;

// Define local time in local time zone
localTime = new DateTimeOffset(new DateTime(2007, 6, 15, 12, 0, 0));
Console.WriteLine("Local time: {0}", localTime);
Console.WriteLine();

// Convert local time to offset 0 and assign to otherTime
otherTime = localTime.ToOffset(TimeSpan.Zero);
Console.WriteLine("Other time: {0}", otherTime);
Console.WriteLine("{0} = {1}: {2}",
                  localTime, otherTime,
                  localTime.Equals(otherTime));
Console.WriteLine("{0} exactly equals {1}: {2}",
                  localTime, otherTime,
                  localTime.EqualsExact(otherTime));
Console.WriteLine();

// Convert other time to UTC
universalTime = localTime.ToUniversalTime();
Console.WriteLine("Universal time: {0}", universalTime);
Console.WriteLine("{0} = {1}: {2}",
                  otherTime, universalTime,
                  universalTime.Equals(otherTime));
Console.WriteLine("{0} exactly equals {1}: {2}",
                  otherTime, universalTime,
                  universalTime.EqualsExact(otherTime));
Console.WriteLine();
// The example produces the following output to the console:
//    Local time: 6/15/2007 12:00:00 PM -07:00
//
//    Other time: 6/15/2007 7:00:00 PM +00:00
//    6/15/2007 12:00:00 PM -07:00 = 6/15/2007 7:00:00 PM +00:00: True
//    6/15/2007 12:00:00 PM -07:00 exactly equals 6/15/2007 7:00:00 PM +00:00: False
//
//    Universal time: 6/15/2007 7:00:00 PM +00:00
//    6/15/2007 7:00:00 PM +00:00 = 6/15/2007 7:00:00 PM +00:00: True
//    6/15/2007 7:00:00 PM +00:00 exactly equals 6/15/2007 7:00:00 PM +00:00: True
Dim localTime, otherTime, universalTime As DateTimeOffset

' Define local time in local time zone
localTime = New DateTimeOffset(#6/15/2007 12:00:00PM#)
Console.WriteLine("Local time: {0}", localTime)
Console.WriteLine()

' Convert local time to offset 0 and assign to otherTime
otherTime = localTime.ToOffset(TimeSpan.Zero)
Console.WriteLine("Other time: {0}", otherTime)
Console.WriteLine("{0} = {1}: {2}", _
                  localTime, otherTime, _
                  localTime.Equals(otherTime))
Console.WriteLine("{0} exactly equals {1}: {2}", _
                  localTime, otherTime, _
                  localTime.EqualsExact(otherTime))
Console.WriteLine()

' Convert other time to UTC
universalTime = localTime.ToUniversalTime()
Console.WriteLine("Universal time: {0}", universalTime)
Console.WriteLine("{0} = {1}: {2}", _
                  otherTime, universalTime, _
                  universalTime.Equals(otherTime))
Console.WriteLine("{0} exactly equals {1}: {2}", _
                  otherTime, universalTime, _
                  universalTime.EqualsExact(otherTime))
Console.WriteLine()
' The example produces the following output to the console:
'    Local time: 6/15/2007 12:00:00 PM -07:00
'    
'    Other time: 6/15/2007 7:00:00 PM +00:00
'    6/15/2007 12:00:00 PM -07:00 = 6/15/2007 7:00:00 PM +00:00: True
'    6/15/2007 12:00:00 PM -07:00 exactly equals 6/15/2007 7:00:00 PM +00:00: False
'    
'    Universal time: 6/15/2007 7:00:00 PM +00:00
'    6/15/2007 7:00:00 PM +00:00 = 6/15/2007 7:00:00 PM +00:00: True
'    6/15/2007 7:00:00 PM +00:00 exactly equals 6/15/2007 7:00:00 PM +00:00: True   

Convertendo UTC em um fuso horário designado

Para converter UTC em hora local, consulte a seção Convertendo UTC em hora local a seguir. Para converter de UTC na hora correspondente em qualquer fuso horário que você designar, chame o método ConvertTimeFromUtc. O método utiliza dois parâmetros:

  • O UTC a ser convertido. Esse deve ser um valor DateTime cuja propriedade Kind está definida como Unspecified ou Utc.

  • O fuso horário no qual o UTC deve ser convertido.

O código a seguir converte o UTC para o Horário Padrão Central:

DateTime timeUtc = DateTime.UtcNow;
try
{
    TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
    DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);
    Console.WriteLine("The date and time are {0} {1}.",
                      cstTime,
                      cstZone.IsDaylightSavingTime(cstTime) ?
                              cstZone.DaylightName : cstZone.StandardName);
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine("The registry does not define the Central Standard Time zone.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine("Registry data on the Central Standard Time zone has been corrupted.");
}
Dim timeUtc As Date = Date.UtcNow
Try
    Dim cstZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")
    Dim cstTime As Date = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone)
    Console.WriteLine("The date and time are {0} {1}.", _
                      cstTime, _
                      IIf(cstZone.IsDaylightSavingTime(cstTime), _
                          cstZone.DaylightName, cstZone.StandardName))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("The registry does not define the Central Standard Time zone.")
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the Central Standard Time zone has been corrupted.")
End Try

Convertendo UTC em hora local

Para converter UTC para a hora local, chame o método ToLocalTime do objeto DateTime cuja hora você deseja converter. O comportamento exato do método depende do valor da propriedade Kind do objeto, conforme mostrado na tabela a seguir:

DateTime.Kind Conversão
DateTimeKind.Local Retorna o valor DateTime inalterado.
DateTimeKind.Unspecified Pressupõe que o valor DateTime está no UTC e o converte do UTC para a hora local.
DateTimeKind.Utc Converte o valor DateTime na hora local.

Observação

O método TimeZone.ToLocalTime se comporta de maneira idêntica ao método DateTime.ToLocalTime. Ele usa um só parâmetro, que é o valor de data e hora a ser convertido.

Você também pode converter a hora em qualquer fuso horário designado em hora local usando o método static (Shared no Visual Basic TimeZoneInfo.ConvertTime ). Esta técnica é abordado na próxima seção.

Convertendo entre dois fusos horários

Você pode converter entre dois fusos horários usando um dos dois seguintes métodos static (Shared no Visual Basic) da classe TimeZoneInfo:

  • ConvertTime

    Os parâmetros desse método são o valor de data e hora a ser convertido, um objeto TimeZoneInfo que representa o fuso horário do valor de data e hora e um objeto TimeZoneInfo que representa o fuso horário no qual o valor de data e hora deverá ser convertido.

  • ConvertTimeBySystemTimeZoneId

    Os parâmetros desse método são o valor de data e hora a ser convertido, o identificador do fuso horário do valor de data e hora e o identificador do fuso horário para o qual converter o valor de data e hora.

Ambos os métodos exigem que a propriedade Kind do valor de data e hora a ser convertido e o objeto TimeZoneInfo ou o identificador de fuso horário que representa seu fuso horário sejam correspondentes. Caso contrário, um ArgumentException será gerado. Por exemplo, se a propriedade Kind do valor de data e hora for DateTimeKind.Local, uma exceção será lançada se o objeto TimeZoneInfo passado como um parâmetro para o método não for igual a TimeZoneInfo.Local. Também é gerada uma exceção se o identificador passado como parâmetro para o método não for igual a TimeZoneInfo.Local.Id.

O exemplo a seguir usa o método ConvertTime para converter da Hora Oficial do Havaí para a hora local:

DateTime hwTime = new DateTime(2007, 02, 01, 08, 00, 00);
try
{
    TimeZoneInfo hwZone = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time");
    Console.WriteLine("{0} {1} is {2} local time.",
            hwTime,
            hwZone.IsDaylightSavingTime(hwTime) ? hwZone.DaylightName : hwZone.StandardName,
            TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local));
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine("The registry does not define the Hawaiian Standard Time zone.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine("Registry data on the Hawaiian Standard Time zone has been corrupted.");
}
Dim hwTime As Date = #2/01/2007 8:00:00 AM#
Try
    Dim hwZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time")
    Console.WriteLine("{0} {1} is {2} local time.", _
                      hwTime, _
                      IIf(hwZone.IsDaylightSavingTime(hwTime), hwZone.DaylightName, hwZone.StandardName), _
                      TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("The registry does not define the Hawaiian Standard Time zone.")
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the Hawaiian Standard Time zone has been corrupted.")
End Try

Convertendo valores de DateTimeOffset

Valores de data e hora representados por objetos DateTimeOffset não são totalmente cientes do fuso horário porque o objeto é desassociado de seu fuso horário no momento em que é instanciado. No entanto, em muitos casos o aplicativo precisa apenas converter uma data e hora com base em dois deslocamentos diferentes do UTC, em vez de na hora em fusos horários específicos. Para realizar essa conversão, você pode chamar o método ToOffset da instância atual. O parâmetro único do método é o deslocamento do novo valor de data e hora que o método retornará.

Por exemplo, se a data e hora da solicitação de um usuário de uma página da Web for conhecida e for serializada como uma cadeia de caracteres no formato MM/dd/aaaa hh:mm:ss zzzz, o seguinte método ReturnTimeOnServer converte esse valor de data e hora para a data e hora no servidor Web:

public DateTimeOffset ReturnTimeOnServer(string clientString)
{
   string format = @"M/d/yyyy H:m:s zzz";
   TimeSpan serverOffset = TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now);

   try
   {
      DateTimeOffset clientTime = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture);
      DateTimeOffset serverTime = clientTime.ToOffset(serverOffset);
      return serverTime;
   }
   catch (FormatException)
   {
      return DateTimeOffset.MinValue;
   }
}
Public Function ReturnTimeOnServer(clientString As String) As DateTimeOffset
    Dim format As String = "M/d/yyyy H:m:s zzz"
    Dim serverOffset As TimeSpan = TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now)

    Try
        Dim clientTime As DateTimeOffset = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture)
        Dim serverTime As DateTimeOffset = clientTime.ToOffset(serverOffset)
        Return serverTime
    Catch e As FormatException
        Return DateTimeOffset.MinValue
    End Try
End Function

Se o método passar a cadeia de caracteres "9/1/2007 5:32:07 -05:00," representando a data e hora em um fuso horário cinco horas anteriores ao UTC, ele retornará “9/1/2007 3:32:07 AM -07:00” para um servidor localizado no fuso horário da Hora Padrão do Pacífico dos EUA.

A classe TimeZoneInfo também inclui uma sobrecarga do método TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) que realiza conversões de fuso horário com valores de ToOffset(TimeSpan). Os parâmetros do método são um valor de DateTimeOffset e uma referência ao fuso horário no qual a hora será convertida. A chamada de método retorna um valor DateTimeOffset. Por exemplo, o método ReturnTimeOnServer no exemplo anterior poderia ser reescrito como segue para chamar o método ConvertTime(DateTimeOffset, TimeZoneInfo).

public DateTimeOffset ReturnTimeOnServer(string clientString)
{
   string format = @"M/d/yyyy H:m:s zzz";

   try
   {
      DateTimeOffset clientTime = DateTimeOffset.ParseExact(clientString, format,
                                  CultureInfo.InvariantCulture);
      DateTimeOffset serverTime = TimeZoneInfo.ConvertTime(clientTime,
                                  TimeZoneInfo.Local);
      return serverTime;
   }
   catch (FormatException)
   {
      return DateTimeOffset.MinValue;
   }
}
Public Function ReturnTimeOnServer(clientString As String) As DateTimeOffset
    Dim format As String = "M/d/yyyy H:m:s zzz"

    Try
        Dim clientTime As DateTimeOffset = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture)
        Dim serverTime As DateTimeOffset = TimeZoneInfo.ConvertTime(clientTime, TimeZoneInfo.Local)
        Return serverTime
    Catch e As FormatException
        Return DateTimeOffset.MinValue
    End Try
End Function

Confira também