Escolher entre DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly e TimeZoneInfo

Os aplicativos do .NET podem usar as informações de data e hora de várias maneiras. Os usos mais comuns das informações de data e hora incluem:

  • Para refletir somente uma data, uma vez que a informação de tempo não é importante.
  • Para refletir somente um tempo, uma vez que a informação de data não é importante.
  • Para refletir uma data abstrata e um tempo que não está vinculado a uma hora e local específicos (por exemplo, a maioria das lojas em um uma cadeia internacional abre em dias da semana às 9h).
  • Para recuperar as informações de data e hora de fontes fora do .NET, normalmente nas quais as informações de data e hora são armazenadas em um tipo de dados simples.
  • Para identificar um único ponto no tempo de maneira única e não ambígua. Alguns aplicativos exigem que a data e a hora sejam inequívocas somente no sistema host. Outros exigem que sejam inequívocas em todos os sistemas (ou seja, uma data serializada em um sistema pode ser significativamente desserializada e usada em outro sistema em qualquer lugar do mundo).
  • Para preservar vários horários relacionados (como o horário local do solicitante e o horário de recebimento de uma solicitação Web pelo servidor).
  • Para realizar a aritmética de data e hora, possivelmente com um resultado que identifica de maneira única e não ambígua um único ponto no tempo.

O .NET inclui os tipos DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly e TimeZoneInfo. Todos eles podem ser usados para compilar aplicativos que funcionam com datas e horas.

Observação

Este artigo não discute o TimeZone, pois a funcionalidade foi quase totalmente incorporada à classe TimeZoneInfo. Sempre que possível, use a classe TimeZoneInfo, ao invés da classe TimeZone.

A estrutura DateTimeOffset

A estrutura DateTimeOffset representa um valor de data e hora, juntamente com um deslocamento que indica quanto o valor difere do UTC. Portanto, o valor sempre identifica sem ambiguidade um único ponto no tempo.

O tipo DateTimeOffset inclui toda a funcionalidade do tipo DateTime, juntamente com reconhecimento de fuso horário. Isso o torna adequado para aplicativos que:

  • Identifique de maneira única e não ambígua um único ponto no tempo. O tipo DateTimeOffset pode ser usado para definir de forma inequívoca o significado de "agora", para registrar os tempos de transação, para registrar os tempos dos eventos de sistema ou de aplicativo e para registrar a criação de arquivos e os tempos de modificação.
  • Realizar aritmética geral de data e hora.
  • Preserve vários tempos relacionados contanto que esses tempos sejam armazenados como dois valores separados ou como dois membros de uma estrutura.

Observação

Esses usos para os valores DateTimeOffset são muito mais comuns do que aqueles para os valores DateTime. Como resultado, considere o DateTimeOffset como o tipo de data e hora padrão para desenvolvimento de aplicativos.

Um valor DateTimeOffset não está vinculado a um fuso horário específico, mas pode ser proveniente de diversos fusos horários. O exemplo a seguir lista os fusos horários aos quais inúmeros valores DateTimeOffset (incluindo a Hora Padrão do Pacífico local) podem pertencer.

using System;
using System.Collections.ObjectModel;

public class TimeOffsets
{
   public static void Main()
   {
      DateTime thisDate = new DateTime(2007, 3, 10, 0, 0, 0);
      DateTime dstDate = new DateTime(2007, 6, 10, 0, 0, 0);
      DateTimeOffset thisTime;

      thisTime = new DateTimeOffset(dstDate, new TimeSpan(-7, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(-6, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(+1, 0, 0));
      ShowPossibleTimeZones(thisTime);
   }

   private static void ShowPossibleTimeZones(DateTimeOffset offsetTime)
   {
      TimeSpan offset = offsetTime.Offset;
      ReadOnlyCollection<TimeZoneInfo> timeZones;

      Console.WriteLine("{0} could belong to the following time zones:",
                        offsetTime.ToString());
      // Get all time zones defined on local system
      timeZones = TimeZoneInfo.GetSystemTimeZones();
      // Iterate time zones
      foreach (TimeZoneInfo timeZone in timeZones)
      {
         // Compare offset with offset for that date in that time zone
         if (timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset))
            Console.WriteLine("   {0}", timeZone.DisplayName);
      }
      Console.WriteLine();
   }
}
// This example displays the following output to the console:
//       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones:
//          (GMT-07:00) Arizona
//          (GMT-08:00) Pacific Time (US & Canada)
//          (GMT-08:00) Tijuana, Baja California
//
//       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones:
//          (GMT-06:00) Central America
//          (GMT-06:00) Central Time (US & Canada)
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old
//          (GMT-06:00) Saskatchewan
//
//       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones:
//          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
//          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
//          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
//          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb
//          (GMT+01:00) West Central Africa
Imports System.Collections.ObjectModel

Module TimeOffsets
    Public Sub Main()
        Dim thisTime As DateTimeOffset

        thisTime = New DateTimeOffset(#06/10/2007#, New TimeSpan(-7, 0, 0))
        ShowPossibleTimeZones(thisTime)

        thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(-6, 0, 0))
        ShowPossibleTimeZones(thisTime)

        thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(+1, 0, 0))
        ShowPossibleTimeZones(thisTime)
    End Sub

    Private Sub ShowPossibleTimeZones(offsetTime As DateTimeOffset)
        Dim offset As TimeSpan = offsetTime.Offset
        Dim timeZones As ReadOnlyCollection(Of TimeZoneInfo)

        Console.WriteLine("{0} could belong to the following time zones:", _
                          offsetTime.ToString())
        ' Get all time zones defined on local system
        timeZones = TimeZoneInfo.GetSystemTimeZones()
        ' Iterate time zones
        For Each timeZone As TimeZoneInfo In timeZones
            ' Compare offset with offset for that date in that time zone
            If timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset) Then
                Console.WriteLine("   {0}", timeZone.DisplayName)
            End If
        Next
        Console.WriteLine()
    End Sub
End Module
' This example displays the following output to the console:
'       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones:
'          (GMT-07:00) Arizona
'          (GMT-08:00) Pacific Time (US & Canada)
'          (GMT-08:00) Tijuana, Baja California
'       
'       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones:
'          (GMT-06:00) Central America
'          (GMT-06:00) Central Time (US & Canada)
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old
'          (GMT-06:00) Saskatchewan
'       
'       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones:
'          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
'          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
'          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
'          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb
'          (GMT+01:00) West Central Africa

A saída mostra que cada valor de data e hora nesse exemplo pode pertencer a pelo menos três fusos horários diferentes. O valor DateTimeOffset de 10/06/2007 mostra que, se um valor de data e hora representa um horário de verão, a diferença UTC não corresponde necessariamente à diferença UTC de base do fuso horário de origem ou à diferença UTC encontrada no nome de exibição. Como um único valor DateTimeOffset não está rigorosamente acoplado ao fuso horário, ele não pode refletir a transição de um fuso horário para o horário de verão. Isso pode ser problemático quando a aritmética de data e hora é usada para manipular um valor DateTimeOffset. Para ver uma discussão sobre como realizar a aritmética de data e hora de uma forma que considere as regras de ajuste de um fuso horário, consulte Executando operações aritméticas com datas e horas.

A estrutura DateTime

Um valor DateTime define a data e a hora específicas. Isso inclui uma propriedade Kind que fornece informações limitadas sobre o fuso horário ao qual a data e a hora pertencem. O valor DateTimeKind retornado pela propriedade Kind indica se o valor DateTime representa a hora local (DateTimeKind.Local), UTC (Tempo Universal Coordenado) (DateTimeKind.Utc) ou uma hora não especificada (DateTimeKind.Unspecified).

A estrutura DateTime é adequada para aplicativos com uma ou mais das seguintes características:

  • Trabalhar com datas e horas abstratas.
  • Trabalhar com datas e horas para as quais as informações de fuso horário estão ausentes.
  • Trabalhar apenas com datas e horas UTC.
  • Realizar aritmética de data e hora, mas que há preocupação com resultados gerais. Por exemplo, em uma operação de adição que soma seis meses a uma data e hora determinada, geralmente não é importante se o resultado é ajustado para horário de verão.

A menos que um valor DateTime específico represente o UTC, geralmente a portabilidade do valor de data e hora é ambígua ou limitada. Por exemplo, se um valor DateTime representa a hora local, ele é portátil nesse fuso horário local (ou seja, se o valor for desserializado em outro sistema no mesmo fuso horário, esse valor ainda identificará de maneira inequívoca um único ponto no tempo). Fora do fuso horário local, esse valor DateTime pode ter várias interpretações. Se a propriedade Kind do valor for DateTimeKind.Unspecified, o valor será ainda menos portátil: agora ele é ambíguo no mesmo fuso horário e possivelmente até no mesmo sistema onde foi serializado pela primeira vez. Somente se um valor DateTime representar o UTC, esse valor identifica de forma inequívoca um único ponto no tempo, independentemente do sistema ou do fuso horário em que o valor é usado.

Importante

Ao salvar ou compartilhar dados DateTime, use UTC e defina a propriedade Kind do valor DateTime como DateTimeKind.Utc.

A estrutura DateOnly

A estrutura DateOnly representa uma data específica, sem a hora. Como não tem nenhum componente de hora, ela representa uma data do início do dia até o final do dia. Essa estrutura é ideal para armazenar datas específicas, como uma data de nascimento, uma data de aniversário, um feriado ou uma data relacionada a negócios.

Embora você possa usar DateTime enquanto ignora o componente de hora, há alguns benefícios em usar DateOnly em vez de DateTime:

  • A estrutura DateTime poderá ser revertida para o dia anterior ou seguinte se for deslocada por um fuso horário. DateOnly não pode ser deslocado por um fuso horário e sempre representa a data que foi definida.
  • Serializar uma estrutura DateTime inclui o componente de hora, que pode obscurecer a intenção dos dados. Além disso, DateOnly serializa menos dados.
  • Quando o código interage com um banco de dados, como o SQL Server, datas inteiras geralmente são armazenadas como o tipo de dados date, que não inclui uma hora. DateOnly corresponde melhor ao tipo de banco de dados.

Para obter mais informações sobre DateOnly, consulte Como usar as estruturas DateOnly e TimeOnly.

Importante

DateOnly não está disponível no .NET Framework.

A estrutura TimeSpan

A estrutura TimeSpan representa um intervalo de tempo. Seus dois usos típicos são:

  • Refletir o intervalo de tempo entre dois valores de data e hora. Por exemplo, subtrair um valor DateTime de outro retorna um valor TimeSpan.
  • Calcular o tempo decorrido. Por exemplo, a propriedade Stopwatch.Elapsedretorna um valor TimeSpan, que reflete o intervalo de tempo decorrido desde a chamada para um dos métodos Stopwatch que começa a medir o tempo decorrido.

Um valor TimeSpan também pode ser usado como uma substituição para um valor DateTime, quando esse valor reflete um tempo sem referência a determinado dia. Esse uso é semelhante às propriedades DateTime.TimeOfDaye DateTimeOffset.TimeOfDay, que retornam um valor TimeSpan que representa o tempo sem referência a uma data. Por exemplo, a estrutura TimeSpan pode ser usada para refletir a hora de abertura ou de fechamento diário de um repositório ou pode ser usada para representar a hora em que qualquer evento regular ocorre.

O exemplo a seguir define uma estrutura StoreInfo que inclui objetos TimeSpan para a hora de abertura e de fechamento do repositório, bem como um objeto TimeZoneInfo que representa o fuso horário do repositório. A estrutura também inclui dois métodos, IsOpenNow e IsOpenAt, que indica se o repositório está aberto em um momento especificado pelo usuário, que se considera estar no fuso horário local.

using System;

public struct StoreInfo
{
   public String store;
   public TimeZoneInfo tz;
   public TimeSpan open;
   public TimeSpan close;

   public bool IsOpenNow()
   {
      return IsOpenAt(DateTime.Now.TimeOfDay);
   }

   public bool IsOpenAt(TimeSpan time)
   {
      TimeZoneInfo local = TimeZoneInfo.Local;
      TimeSpan offset = TimeZoneInfo.Local.BaseUtcOffset;

      // Is the store in the same time zone?
      if (tz.Equals(local)) {
         return time >= open & time <= close;
      }
      else {
         TimeSpan delta = TimeSpan.Zero;
         TimeSpan storeDelta = TimeSpan.Zero;

         // Is it daylight saving time in either time zone?
         if (local.IsDaylightSavingTime(DateTime.Now.Date + time))
            delta = local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;

         if (tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(DateTime.Now.Date + time, local, tz)))
            storeDelta = tz.GetAdjustmentRules()[tz.GetAdjustmentRules().Length - 1].DaylightDelta;

         TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
         return comparisonTime >= open & comparisonTime <= close;
      }
   }
}
Public Structure StoreInfo
    Dim store As String
    Dim tz As TimeZoneInfo
    Dim open As TimeSpan
    Dim close As TimeSpan

    Public Function IsOpenNow() As Boolean
        Return IsOpenAt(Date.Now.TimeOfDay)
    End Function

    Public Function IsOpenAt(time As TimeSpan) As Boolean
        Dim local As TimeZoneInfo = TimeZoneInfo.Local
        Dim offset As TimeSpan = TimeZoneInfo.Local.BaseUtcOffset

        ' Is the store in the same time zone?
        If tz.Equals(local) Then
            Return time >= open AndAlso time <= close
        Else
            Dim delta As TimeSpan = TimeSpan.Zero
            Dim storeDelta As TimeSpan = TimeSpan.Zero

            ' Is it daylight saving time in either time zone?
            If local.IsDaylightSavingTime(Date.Now.Date + time) Then
                delta = local.GetAdjustmentRules(local.GetAdjustmentRules().Length - 1).DaylightDelta
            End If
            If tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(Date.Now.Date + time, local, tz))
                storeDelta = tz.GetAdjustmentRules(tz.GetAdjustmentRules().Length - 1).DaylightDelta
            End If
            Dim comparisonTime As TimeSpan = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate
            Return (comparisonTime >= open AndAlso comparisonTime <= close)
        End If
    End Function
End Structure

A estrutura StoreInfo pode ser usada pelo código do cliente como o exposto a seguir.

public class Example
{
   public static void Main()
   {
      // Instantiate a StoreInfo object.
      var store103 = new StoreInfo();
      store103.store = "Store #103";
      store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
      // Store opens at 8:00.
      store103.open = new TimeSpan(8, 0, 0);
      // Store closes at 9:30.
      store103.close = new TimeSpan(21, 30, 0);

      Console.WriteLine("Store is open now at {0}: {1}",
                        DateTime.Now.TimeOfDay, store103.IsOpenNow());
      TimeSpan[] times = { new TimeSpan(8, 0, 0), new TimeSpan(21, 0, 0),
                           new TimeSpan(4, 59, 0), new TimeSpan(18, 31, 0) };
      foreach (var time in times)
         Console.WriteLine("Store is open at {0}: {1}",
                           time, store103.IsOpenAt(time));
   }
}
// The example displays the following output:
//       Store is open now at 15:29:01.6129911: True
//       Store is open at 08:00:00: True
//       Store is open at 21:00:00: False
//       Store is open at 04:59:00: False
//       Store is open at 18:31:00: False
Module Example
    Public Sub Main()
        ' Instantiate a StoreInfo object.
        Dim store103 As New StoreInfo()
        store103.store = "Store #103"
        store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time")
        ' Store opens at 8:00.
        store103.open = new TimeSpan(8, 0, 0)
        ' Store closes at 9:30.
        store103.close = new TimeSpan(21, 30, 0)

        Console.WriteLine("Store is open now at {0}: {1}",
                          Date.Now.TimeOfDay, store103.IsOpenNow())
        Dim times() As TimeSpan = {New TimeSpan(8, 0, 0),
                                    New TimeSpan(21, 0, 0),
                                    New TimeSpan(4, 59, 0),
                                    New TimeSpan(18, 31, 0)}
        For Each time In times
            Console.WriteLine("Store is open at {0}: {1}",
                              time, store103.IsOpenAt(time))
        Next
    End Sub
End Module
' The example displays the following output:
'       Store is open now at 15:29:01.6129911: True
'       Store is open at 08:00:00: True
'       Store is open at 21:00:00: False
'       Store is open at 04:59:00: False
'       Store is open at 18:31:00: False

A estrutura TimeOnly

A estrutura TimeOnly representa um valor de hora do dia, como um despertador diário ou a hora em que você almoça todos os dias. TimeOnly é limitado ao intervalo de 00:00:00.0000000 - 23:59:59.9999999, uma hora específica do dia.

Antes do tipo TimeOnly ser introduzido, os programadores normalmente usavam o tipo DateTime ou o tipo TimeSpan para representar uma hora específica. No entanto, usar essas estruturas para simular uma hora sem uma data pode introduzir alguns problemas, o que TimeOnly resolve:

  • TimeSpan representa o tempo decorrido, como o tempo medido com um cronômetro. O intervalo superior é de mais de 29.000 anos, e seu valor pode ser negativo para indicar a movimentação para trás no tempo. Um TimeSpan negativo não indica uma hora específica do dia.
  • Se TimeSpan for usado como uma hora do dia, há o risco de que possa ser manipulado para um valor fora do dia de 24 horas. TimeOnly não tem esse risco. Por exemplo, se o turno de trabalho de um funcionário começar às 18:00 e durar 8 horas, adicionar 8 horas à estrutura TimeOnly será revertido para 2:00.
  • Usar DateTime para uma hora do dia requer que uma data arbitrária seja associada à hora e, em seguida, seja desconsiderada. É uma prática comum escolher DateTime.MinValue (0001-01-01) como a data; no entanto, se horas forem subtraídas do valor DateTime, uma exceção OutOfRange poderá ocorrer. TimeOnly não tem esse problema, pois o tempo decorre para frente e para trás em torno do período de 24 horas.
  • Serializar uma estrutura DateTime inclui o componente de data, que pode obscurecer a intenção dos dados. Além disso, TimeOnly serializa menos dados.

Para obter mais informações sobre TimeOnly, consulte Como usar as estruturas DateOnly e TimeOnly.

Importante

TimeOnly não está disponível no .NET Framework.

A classe TimeZoneInfo

A classe TimeZoneInfo representa qualquer um dos fusos horários da Terra e permite a conversão de qualquer data e hora em um fuso horário para o equivalente em outro fuso horário. A classe TimeZoneInfo possibilita trabalhar com datas e horas de modo que qualquer valor de data e hora identifique de forma inequívoca um único ponto no tempo. A classe TimeZoneInfo também é extensível. Embora dependa das informações de fuso horário fornecidas para sistemas Windows e definidas no registro, ela permite a criação de fusos horários personalizados. Ela também permite a serialização e desserialização das informações de fuso horário.

Em alguns casos, aproveitar ao máximo a classe TimeZoneInfo pode exigir um trabalho adicional de desenvolvimento. Se os valores de data e hora não forem rigorosamente acoplados aos fusos horários aos quais pertencem, um trabalho adicional será necessário. A menos que o aplicativo ofereça um mecanismo para vincular a data e hora ao fuso horário associado, é fácil para determinado valor de data e hora se desassociar do fuso horário. Um método de vinculação dessas informações é definir uma classe ou estrutura que contenha o valor de data e hora e seu objeto de fuso horário associado.

Para aproveitar o suporte a fuso horário no .NET, você deve saber o fuso horário a que um valor de data e hora pertence, quando esse objeto de data e hora for instanciado. Muitas vezes, o fuso horário não é conhecido, especialmente em aplicativos Web ou de rede.

Confira também