Práticas recomendadas de codificação usando DateTime no .NET Framework

 

Dan Rogers
Microsoft Corporation

Fevereiro de 2004

Aplica-se a
   Microsoft® .NET Framework
   Microsoft® ASP.NET Web Services
   Serialização XML

Resumo: Escrever programas que armazenam, executam cálculos e serializam valores de hora usando o tipo DateTime no Microsoft .NET Framework requer uma consciência dos diferentes problemas associados às representações de tempo disponíveis no Windows e no .NET. Este artigo se concentra nos principais cenários de teste e desenvolvimento envolvendo o tempo e define as recomendações de práticas recomendadas para escrever programas que usam o tipo DateTime na Microsoft . Assemblies e aplicativos baseados em NET. (18 páginas impressas)

Sumário

Segundo plano
   O que é datetime, afinal?
   As regras
Estratégias de armazenamento
   Prática recomendada nº 1
   Prática recomendada nº 2
Executando cálculos
   Don't Get Fooled Again
   Prática recomendada nº 3
   Classificando métodos DateTime
O caso especial de XML
   Prática recomendada nº 4
   Prática recomendada nº 5
O Dilema dos Codificadores de Classe
   Prática recomendada nº 6
Lidar com o horário de verão
   Prática recomendada nº 7
Formatação e análise de valores de User-Ready
   Consideração futura
Problemas com o método DateTime.Now()
   Prática recomendada nº 8
Um par de extras pouco conhecidos
Conclusão

Segundo plano

Muitos programadores encontram atribuições que exigem que eles armazenem e processem dados com precisão que contêm informações de data e hora. À primeira vista, o tipo de dados DateTime do CLR (Common Language Runtime ) parece ser perfeito para essas tarefas. No entanto, não é incomum que os programadores, mas os testadores mais prováveis, encontrem casos em que um programa simplesmente perde o controle dos valores de tempo corretos. Este artigo se concentra em problemas associados à lógica que envolve DateTime e, ao fazer isso, descobre as práticas recomendadas para escrever e testar programas que capturam, armazenam, recuperam e transmitem informações de DateTime.

O que é datetime, afinal?

Quando analisamos a documentação da biblioteca de classes do NET Framework, vemos que "O tipo de valor System.DateTime do CLR representa datas e horários que variam de 12:00:00 meia-noite, 1º de janeiro de 0001 AD às 23:59:59, 31 de dezembro de 9999 AD." Lendo ainda mais, aprendemos, sem surpresas, que um valor DateTime representa um instantâneo em um ponto no tempo, e que uma prática comum é registrar valores pontuais no UCT (Tempo Universal Coordenado) — mais comumente conhecido como Gmt (Horário Médio de Greenwich).

À primeira vista, então, um programador descobre que um tipo DateTime é muito bom em armazenar valores de tempo que provavelmente serão encontrados em problemas de programação atuais, como em aplicativos de negócios. Com essa confiança, muitos programadores desavisados começam a codificar, confiantes de que podem aprender o quanto precisarem de tempo à medida que avançam. Essa abordagem de "aprender conforme o uso" pode levar você a alguns problemas, portanto, vamos começar a identificá-los. Eles vão desde problemas na documentação até comportamentos que precisam ser fatorados nos designs do programa.

A documentação V1.0 e 1.1 para System.DateTime faz algumas generalizações que podem tirar o programador desavisado dos trilhos. Por exemplo, a documentação atualmente ainda diz que os métodos e as propriedades encontrados na classe DateTime sempre usam a suposição de que o valor representa o fuso horário local do computador local ao fazer cálculos ou comparações. Essa generalização acaba por ser falsa porque há determinados tipos de cálculos de Data e Hora que pressupõem GMT e outros que assumem uma exibição de fuso horário local. Essas áreas são apontadas posteriormente neste artigo.

Portanto, vamos começar explorando o tipo DateTime delineando uma série de regras e práticas recomendadas que podem ajudá-lo a fazer com que seu código funcione corretamente na primeira vez.

As regras

  1. Cálculos e comparações de instâncias DateTime só são significativos quando as instâncias que estão sendo comparadas ou usadas são representações de pontos no tempo da mesma perspectiva de fuso horário.
  2. Um desenvolvedor é responsável por manter o controle das informações de fuso horário associadas a um valor DateTime por meio de algum mecanismo externo. Normalmente, isso é feito definindo outro campo ou variável que você usa para registrar informações de fuso horário ao armazenar um tipo de valor DateTime. Essa abordagem (armazenar o sentido de fuso horário ao lado do valor DateTime) é a mais precisa e permite que diferentes desenvolvedores em diferentes pontos no ciclo de vida de um programa sempre tenham uma compreensão clara do significado de um valor DateTime. Outra abordagem comum é torná-la uma "regra" em seu design de que todos os valores de tempo são armazenados em um contexto de fuso horário específico. Essa abordagem não requer armazenamento adicional para salvar a exibição de um usuário do contexto de fuso horário, mas introduz o risco de que um valor de tempo seja interpretado incorretamente ou armazenado incorretamente por um desenvolvedor que não esteja ciente da regra.
  3. Executar cálculos de data e hora em valores que representam a hora local do computador pode nem sempre produzir o resultado correto. Ao executar cálculos em valores de hora em contextos de fuso horário que praticam o horário de verão, você deve converter valores em representações de tempo universal antes de executar cálculos aritméticos de data. Para obter uma lista específica de operações e contextos de fuso horário adequados, consulte a tabela na seção Classificando métodos DateTime.
  4. Um cálculo em uma instância de um valor DateTime não modifica o valor da instância, portanto, uma chamada para MyDateTime.ToLocalTime() não modifica o valor da instância do DateTime. Os métodos associados às classes Date (no Visual Basic®) e DateTime (no .NET CLR) retornam novas instâncias que representam o resultado de um cálculo ou operação.
  5. Ao usar o .NET Framework versão 1.0 e 1.1, NÃO envie um valor DateTime que represente a hora uct por meio deSystem.XML. Serialização. Isso vale para valores de Data, Hora e DateTime. Para serviços Web e outras formas de serialização para XML envolvendo System.DateTime, sempre verifique se o valor no valor DateTime representa a hora local do computador atual. O serializador decodificará corretamente um valor DateTime definido pelo esquema XML codificado em GMT (valor de deslocamento = 0), mas o decodificará para o ponto de vista de hora do computador local.
  6. Em geral, se você estiver lidando com o tempo decorrido absoluto, como medir um tempo limite, executar aritmética ou fazer comparações de diferentes valores datetime, tente usar um valor de tempo universal, se possível, para obter a melhor precisão possível sem efeitos de fuso horário e/ou horário de verão tendo um impacto.
  7. Ao lidar com conceitos voltados para o usuário de alto nível, como agendamento, e você pode assumir com segurança que cada dia tem 24 horas da perspectiva de um usuário, pode ser bom contrariar a Regra nº 6 executando aritmética, et cetera, em horários locais.

Ao longo deste artigo, essa lista simples de regras serve como base para um conjunto de práticas recomendadas para escrever e testar aplicativos que processam datas.

Agora, vários de vocês já estão olhando através de seu código e dizendo: "Oh darn, ele não está fazendo o que eu esperava que ele fizesse", que é o propósito deste artigo. Para aqueles de nós que não tiveram uma epifania desde a leitura até aqui, vamos dar uma olhada nos problemas associados ao processamento de valores DateTime (a partir de agora, vou reduzir isso para "datas") em . Aplicativos baseados em NET.

Estratégias de armazenamento

De acordo com as regras acima, os cálculos sobre valores de data só são significativos quando você entende as informações de fuso horário associadas ao valor de data que você está processando. Isso significa que, se você estiver armazenando seu valor temporariamente em uma variável de membro de classe ou optando por salvar os valores coletados em um banco de dados ou arquivo, você, como programador, é responsável por aplicar uma estratégia que permite que as informações de fuso horário associadas sejam compreendidas posteriormente.

Prática recomendada nº 1

Ao codificar, armazene as informações de fuso horário associadas a um tipo DateTime em uma variável adjunta.

Uma estratégia alternativa, mas menos confiável, é tornar uma regra firme de que suas datas armazenadas sempre serão convertidas em um fuso horário específico, como GMT, antes do armazenamento. Isso pode parecer sensato, e muitas equipes podem fazê-lo funcionar. No entanto, a falta de um sinal evidente que diz que uma coluna DateTime específica em uma tabela em um banco de dados está em um fuso horário específico invariavelmente leva a erros de interpretação em iterações posteriores de um projeto.

Uma estratégia comum vista em uma pesquisa informal de diferentes . Aplicativos baseados em NET são o desejo de sempre ter datas representadas no horário universal (GMT). Eu digo "desejo" porque isso nem sempre é prático. Um caso em questão surge ao serializar uma classe que tem uma variável de membro DateTime por meio de um serviço Web. O motivo é que um tipo de valor DateTime é mapeado para um tipo XSD:DateTime (como se esperaria) e o tipo XSD acomoda a representação de pontos no tempo em qualquer fuso horário. Discutiremos o caso XML mais tarde. Mais interessante, uma boa porcentagem desses projetos não estava realmente atingindo sua meta e estavam armazenando as informações de data no fuso horário do servidor sem perceber.

Nesses casos, um fato interessante é que os testadores não estavam vendo problemas de conversão de tempo, portanto, ninguém havia notado que o código que deveria converter as informações de data local em hora uct estava falhando. Nesses casos específicos, os dados foram serializados posteriormente por meio de XML e foram convertidos corretamente porque as informações de data estavam no computador no horário local para começar.

Vamos examinar um código que não funciona:

Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM") 'date assignment
d.ToUniversalTime()

O programa acima usa o valor na variável d e o salva em um banco de dados, esperando que o valor armazenado represente uma exibição uct de tempo. Este exemplo reconhece que o método Parse renderiza o resultado em hora local, a menos que alguma cultura não padrão seja usada como um argumento opcional para a família parse de métodos.

O código mostrado anteriormente realmente falha ao converter o valor na variável DateTime d para tempo universal na terceira linha porque, conforme escrito, o exemplo viola a Regra nº 4 (os métodos na classe DateTime não convertem o valor subjacente). Observação: esse código foi visto em um aplicativo real que havia sido testado.

Como ele passou? Os aplicativos envolvidos conseguiram comparar com êxito as datas armazenadas porque, durante o teste, todos os dados eram provenientes de computadores definidos como o mesmo fuso horário, portanto, a Regra nº 1 foi atendida (todas as datas que estão sendo comparadas e calculadas são localizadas para o mesmo ponto de exibição de fuso horário). O bug nesse código é do tipo difícil de detectar — uma instrução que é executada, mas que não faz nada (dica: a última instrução no exemplo é uma no-op como escrita).

Prática recomendada nº 2

Ao testar, marcar ver que os valores armazenados representam o valor pontual que você pretende no fuso horário que você pretende.

Corrigir o exemplo de código é fácil:

Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM").ToUniversalTime()

Como os métodos de cálculo associados ao tipo de valor DateTime nunca afetam o valor subjacente, mas retornam o resultado do cálculo, um programa deve se lembrar de armazenar o valor convertido (se isso for desejado, é claro). Em seguida, examinaremos como até mesmo esse cálculo aparentemente adequado pode não conseguir os resultados esperados em determinadas circunstâncias envolvendo o horário de verão.

Executando cálculos

À primeira vista, as funções de cálculo fornecidas com a classe System.DateTime são realmente úteis. O suporte é fornecido para adicionar intervalos a valores de tempo, executar aritmética em valores de tempo e até mesmo converter valores de tempo do .NET para o tipo de valor correspondente apropriado para chamadas à API Win32®, bem como chamadas de Automação OLE. Uma olhada nos métodos de suporte que envolvem o tipo DateTime evoca uma visão nostálgica das diferentes maneiras pelas quais o MS-DOS® e o Windows® evoluíram para lidar com tempos e carimbos de data/hora ao longo dos anos.

O fato de que todos esses componentes ainda estão presentes em várias partes do sistema operacional está relacionado aos requisitos de compatibilidade com versões anteriores que a Microsoft mantém. Para um programador, isso significa que, se você estiver movendo dados que representam carimbos de data/hora em arquivos, diretórios ou fazendo interoperabilidade COM/OLE envolvendo valores date e DateTime, você precisará se tornar proficiente ao lidar com conversões entre as diferentes gerações de tempo presentes no Windows.

Não se engane novamente

Vamos supor que você adotou a estratégia "armazenamos tudo no horário UCT", presumivelmente para evitar a sobrecarga de ter que armazenar um deslocamento de fuso horário (e talvez uma exibição com olhos do usuário do fuso horário, como Hora Padrão do Pacífico ou PST). Há várias vantagens em executar cálculos usando o tempo UCT. O principal deles é o fato de que, quando representado no tempo universal, todos os dias tem um comprimento fixo e não há deslocamentos de fuso horário para lidar.

Se você ficou surpreso ao ler que um dia pode ter comprimentos diferentes, lembre-se de que em qualquer fuso horário que permita o horário de verão, em dois dias do ano (normalmente), os dias têm um comprimento diferente. Portanto, mesmo que você esteja usando um valor de hora local, como o Horário Padrão do Pacífico (PST), se você tentar adicionar um período de tempo a um valor específico da instância datetime, talvez não obtenha o resultado que pensou que deveria se o intervalo que está sendo adicionado o levar além do tempo de alteração em uma data em que o horário de verão seja iniciado ou encerrado.

Vamos examinar um exemplo de código que não funciona no fuso horário do Pacífico no Estados Unidos:

Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.AddHours(3.0)
' - displays 10/26/2003 03:00:00 AM – an ERROR!
MsgBox(d.ToString)

O resultado exibido desse cálculo pode parecer correto à primeira vista; no entanto, em 26 de outubro de 2003, um minuto após 1:59 PST, a alteração do horário de verão entrou em vigor. A resposta correta deveria ter sido 26/10/2003, 02:00:00, portanto, esse cálculo com base em um valor de hora local não resultou no resultado correto. Mas se olharmos para trás na Regra nº 3, parece que temos uma contradição, mas não temos. Vamos apenas chamá-lo de um caso especial para usar os métodos Adicionar/Subtrair em fusos horários que celebram o horário de verão.

Prática recomendada nº 3

Ao codificar, tenha cuidado se precisar executar cálculos de DateTime (adicionar/subtrair) em valores que representam fusos horários que praticam o horário de verão. Erros de cálculo inesperados podem resultar. Em vez disso, converta o valor de hora local em tempo universal, execute o cálculo e converta de volta para obter a precisão máxima.

Corrigir esse código desfeito é simples:

Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.ToUniversalTime().AddHours(3.0).ToLocalTime()
' - displays 10/26/2003 02:00:00 AM – Correct!
MsgBox(d.ToString)
  

A maneira mais fácil de adicionar intervalos de tempo de forma confiável é converter valores baseados em tempo local em tempo universal, executar os cálculos e converter os valores novamente.

Classificando métodos DateTime

Ao longo deste artigo, diferentes métodos de classe System.DateTime são discutidos. Alguns geram um resultado correto quando a instância subjacente representa a hora local, algumas quando representam o Tempo Universal e outras ainda não exigem nenhuma instância subjacente. Além disso, alguns são completamente independentes do fuso horário (por exemplo, AddYear, AddMonth). Para simplificar a compreensão geral das suposições por trás dos métodos de suporte datetime mais comumente encontrados, a tabela a seguir é fornecida.

Para ler a tabela, considere o ponto de vista inicial (entrada) e final (valor retornado). Em todos os casos, o estado final de chamar um método é retornado pelo método . Nenhuma conversão é feita na instância subjacente dos dados. Também são fornecidas advertências que descrevem exceções ou diretrizes úteis.

Nome do método Ponto de Vista Inicial Ponto de Vista Final Advertências
Touniversaltime Hora local UTC Não chame em uma instância DateTime que já represente o Tempo Universal
Tolocaltime UTC Hora local Não chame em uma instância datetime que já representa a hora local
Tofiletime Hora local   O método retorna um INT64 que representa a hora do arquivo Win32 (hora uct)
Fromfiletime   Hora local Método estático — nenhuma instância necessária. Usa um tempo DE UCT INT64 como entrada
Tofiletimeutc

(Somente V1.1)

UTC   O método retorna um INT64 que representa um tempo de arquivo Win32 (hora UCT)
FromFileTimeUtc

(Somente V1.1)

  UTC O método converte a hora do arquivo INT64 Win32 em uma instância de UCT DateTime
Agora   Hora local Método estático — nenhuma instância necessária. Retorna um DateTime que representa a hora atual na hora do computador local
UtcNow   UTC Método estático — nenhuma instância necessária
Isleapyear Hora local   Retorna Boolean que indica true se a parte do ano da instância de hora local for um ano bissexto.
Hoje   Hora local Método estático — nenhuma instância necessária. Retorna um DateTime definido como Meia-noite do dia atual no horário do computador local.

O caso especial de XML

Várias pessoas com quem falei recentemente tinham a meta de design de serializar valores de tempo em serviços Web, de modo que o XML que representa o DateTime seria formatado em GMT (por exemplo, com um deslocamento zero). Embora eu tenha ouvido várias razões que vão desde o desejo de simplesmente analisar o campo como uma cadeia de caracteres de texto para exibição em um cliente até querer preservar as suposições "armazenadas no UCT" que existem no servidor para os chamadores de serviços Web, não me convenci de que há uma boa razão para controlar o formato de marshalling no fio até esse ponto. Por quê? Simplesmente porque a codificação XML para um tipo DateTime é perfeitamente adequada para representar um instante no tempo, e o serializador XML integrado ao .NET Framework faz um bom trabalho de gerenciar os problemas de serialização e desserialização associados a valores de tempo.

Além disso, acontece que forçar o System.XML. O serializador de serialização para codificar um valor de data em GMT na transmissão não é possível no .NET, pelo menos não hoje. Como programador, designer ou gerente de projeto, seu trabalho se torna garantir que os dados que estão sendo passados em seu aplicativo sejam executados com precisão com um custo mínimo.

Vários dos grupos com quem conversei na pesquisa que entrou neste artigo adotaram a estratégia de definir classes especiais e escrever seus próprios serializadores XML para que eles tenham controle total sobre como eram os valores datetime no fio em seu XML. Embora eu admire a arrancada que os desenvolvedores têm ao dar o salto para este empreendimento corajoso, tenha certeza de que as nuances de lidar com o horário de verão e os problemas de conversão de fuso horário por si só devem fazer um bom gerente dizer: "De jeito nenhum", especialmente quando os mecanismos fornecidos no .NET Framework fazer um trabalho perfeitamente preciso de serializar valores de tempo já.

Há apenas um truque que você precisa estar ciente e, como designer, você DEVE entender isso e aderir à regra (consulte Regra nº 5).

Código que não funciona:

Primeiro, vamos definir uma classe XML simples com uma variável de membro DateTime. Para fins de integridade, essa classe é o equivalente simplificado da abordagem recomendada ilustrada posteriormente no artigo.

<XmlType(TypeName:="timeTestDef", _
    Namespace:= "http://tempuri.org/Timetester.xsd")>), _
    XmlRoot(), Serializable()> _
Public Class timeTestDef
    Private __timeVal As DateTime

    <XmlIgnore()> _
    Public timeValSpecified As Boolean

    <XmlElement(ElementName:="timeVal", IsNullable:=False, _
        Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
        Namespace:="http://tempuri.org/Timetester.xsd")> _
    Public Property timeVal() As DateTime
        Get
            timeVal = __timeVal
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value
            timeValSpecified = True
        End Set
    End Property
End Class

Agora, vamos usar essa classe para gravar alguns XML em um arquivo.

' write out to the file
Dim t As Xml.XmlTextWriter
Dim ser As XmlSerializer
Dim tt As New timeTest ' a class that has a DateTime variable
' set the fields in your class
tt.timeVal = DateTime.Parse("12/12/2003 12:01:02 PM")
tt.timeVal = tt.TimeVal.ToUniversalTime()

' get a serializer for the root type, and serialize this UTC time
ser = New XmlSerializer(GetType(timeTest))
t = New Xml.XmlTextWriter("c:\timetest.xml", System.Text.Encoding.UTF8)
ser.Serialize(t, tt)
t.Close()
t = Nothing
tt = Nothing

Quando esse código é executado, o XML serializado para o arquivo de saída contém uma representação datetime XML da seguinte maneira:

<timeVal>2003-12-12T20:01:02.0000000-08:00</timeVal> 

Este é um erro: o valor codificado no XML está desativado em oito horas! Como esse é o deslocamento de fuso horário do meu computador atual, devemos suspeitar. Olhando para o XML em si, a data está certa, e a data de 20:01:02 corresponde à hora do relógio em Londres para o meu próprio meio-dia, mas a parte de deslocamento não está correta para um relógio com sede em Londres. Quando o XML se parece com o horário de Londres, o deslocamento também deve representar o ponto de vista de Londres, que esse código não alcança.

O serializador XML sempre pressupõe que os valores datetime que estão sendo serializados representam a hora do computador local, portanto, ele aplica o deslocamento de fuso horário local do computador como a parte de deslocamento da hora XML codificada. Quando desserializamos isso em outro computador, o deslocamento original é subtraído do valor que está sendo analisado e o deslocamento de fuso horário do computador atual é adicionado.

Quando começamos com uma hora local, o resultado da serialização (codificar para DateTime XML seguido de decodificar para a hora do computador local) é sempre correto, mas somente se o valor de DateTime inicial sendo serializado representar a hora local quando a serialização começar. No caso desse exemplo de código desfeito, já tínhamos ajustado o valor DateTime na variável de membro timeVal para a hora UCT, portanto, quando serializamos e desserializamos, o resultado é desativado pelo número de horas igual ao deslocamento de fuso horário do computador de origem. Isso é ruim.

Prática recomendada nº 4

Ao testar, calcule o valor que você espera ver na cadeia de caracteres XML serializada usando uma exibição de hora local do computador do ponto no tempo que está sendo testado. Se o XML no fluxo de serialização for diferente, registre um bug!

Corrigir esse código é simples. Comente a linha que chama ToUniversalTime().

Prática recomendada nº 5

Ao escrever código para serializar classes que têm variáveis de membro DateTime, os valores devem representar a hora local. Se eles não contiverem hora local, ajuste-os antes de qualquer etapa de serialização, incluindo passar ou retornar tipos que contêm valores DateTime em serviços Web.

O quandário de codificadores de classe

Anteriormente, vimos uma classe bastante não sofisticada que expôs uma propriedade DateTime. Nessa classe, simplesmente serializamos o que armazenamos em um DateTime, sem considerar se o valor representava um ponto de vista de tempo local ou universal. Vamos examinar uma abordagem mais sofisticada que oferece aos programadores uma escolha geral sobre quais suposições de fuso horário eles desejam, ao mesmo tempo em que estão sempre serializando corretamente.

Ao codificar uma classe que terá uma variável de membro do tipo DateTime, um programador tem a opção de tornar a variável de membro pública ou escrever a lógica de propriedade para encapsular a variável de membro com operações get/set . Optar por tornar o tipo público tem várias desvantagens que, no caso de tipos DateTime, podem ter consequências que não estão sob o controle do desenvolvedor da classe.

Usando o que aprendemos até agora, considere fornecer duas propriedades para cada tipo DateTime.

O exemplo a seguir ilustra a abordagem recomendada para gerenciar variáveis de membro DateTime:

<XmlType(TypeName:="timeTestDef", _
    Namespace:= "http://tempuri.org/Timetester.xsd")>), _
    XmlRoot(), Serializable(), _
    EditorBrowsable(EditorBrowsableState.Advanced)> _
Public Class timeTestDef
    Private __timeVal As DateTime

    <XmlIgnore()> _
    Public timeValSpecified As Boolean

    <XmlElement(ElementName:="timeVal", IsNullable:=False, _
        Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
        Namespace:="http://tempuri.org/Timetester.xsd")> _
    Public Property timeVal() As DateTime
        Get
            timeVal = __timeVal.ToLocalTime()
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value.ToUniversalTime()
            timeValSpecified = True
        End Set
    End Property

    <XmlIgnore()> _
    Public Property timeValUTC() As DateTime
        Get
            timeValUTC = __timeVal
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value
            timeValSpecified = True
        End Set
    End Property
End Class

Este exemplo é o equivalente corrigido ao exemplo de serialização de classe anterior. Em ambos os exemplos de classe (este e o anterior), as classes são implementações descritas com o seguinte esquema:

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema id="Timetester" 
     targetNamespace="http://tempuri.org/Timetester.xsd"
     elementFormDefault="qualified"
     xmlns="http://tempuri.org/Timetester.xsd"
     xmlns:mstns="http://tempuri.org/Timetester.xsd"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">

     <xs:element name="timeTest" type="timeTestDef"/>
     <xs:complexType name="timeTestDef">
      <xs:sequence>
         <xs:element name="timeVal" type="xs:dateTime"/>
      </xs:sequence>
     </xs:complexType>
</xs:schema>

Nesse esquema e em qualquer implementação de classe, definimos uma variável de membro que representa um valor de tempo opcional. Em nosso exemplo recomendado, fornecemos duas propriedades com getters e setters: uma para o tempo universal e outra para a hora local. Os atributos com colchetes angulares que você vê no código dizem ao serializador XML para usar a versão de hora local para serialização e geralmente fazem com que a implementação da classe resulte em saída em conformidade com o esquema. Para fazer com que a classe lide corretamente com a falta de expressão opcional quando nenhum valor é definido na instância, a variável timeValSpecified e a lógica associada no setter de propriedade controla se o elemento XML é expresso no momento da serialização ou não. Esse comportamento opcional explora um recurso no subsistema de serialização que foi projetado para dar suporte a conteúdo XML opcional.

Usar essa abordagem para gerenciar valores DateTime em suas classes .NET oferece o melhor dos dois mundos: você obtém acesso de armazenamento com base no tempo universal para que os cálculos sejam precisos e você obtenha a serialização adequada das exibições de hora local.

Prática recomendada nº 6

Ao codificar, torne as variáveis de membro DateTime privadas e forneça duas propriedades para manipular seus membros DateTime em tempo local ou universal. Reduza o armazenamento no membro privado como tempo UCT controlando a lógica em seus getters e setters. Adicione os atributos de serialização XML à declaração de propriedade de hora local para garantir que o valor de hora local seja o que é serializado (veja o exemplo).

Ressalvas a essa abordagem

A abordagem recomendada de gerenciar um DateTime no Tempo Universal em suas variáveis de membro privado é sólida, assim como a recomendação para fornecer propriedades duplas para permitir que os codificadores lidem com as versões de tempo com as quais eles estão mais confortáveis. Um problema em que um desenvolvedor usa essa ou qualquer outra abordagem que exponha qualquer hora local a um programa continua sendo o problema de 25 horas por dia em torno do horário de verão. Isso continuará sendo um problema para programas que usam o CLR versão 1.0 e 1.1, portanto, você precisa estar ciente de se o programa se enquadra nesse caso especial (a hora adicionada ou ausente para o tempo que está sendo representado) e se ajustar manualmente. Para aqueles que não podem tolerar uma janela de problemas de uma hora por ano, a recomendação atual é armazenar suas datas como cadeias de caracteres ou alguma outra abordagem autogerenciada. (Inteiros longos do Unix são uma boa opção.)

Para CLR versão 2.0 (disponível na próxima versão do Visual Studio® com o nome de código "Whidbey"), o reconhecimento de se um DateTime contém uma hora local ou um valor de tempo universal está sendo adicionado ao .NET Framework. Nesse ponto, o padrão recomendado continuará funcionando, mas para programas que interagem com variáveis de membro por meio das propriedades UTC, esses erros no período de hora ausente/extra serão eliminados. Por esse motivo, a prática recomendada para codificação usando propriedades duplas é altamente sugerida hoje, para que seus programas migrem corretamente para o CLR versão 2.0.

Lidando com o Horário de Verão

À medida que nos preparamos para fechar e deixar o tópico de práticas de codificação e teste para valores DateTime, ainda há um caso especial que você precisa entender. Esse caso envolve as ambiguidades que envolvem o horário de verão e o problema repetido de uma hora por ano. Esse problema é principalmente aquele que afeta apenas aplicativos que coletam valores de tempo da entrada do usuário.

Para aqueles de vocês na maioria da contagem de países, este caso é trivial porque na maioria dos países o horário de verão não é praticado. Mas para aqueles de vocês que estão na maioria dos programas afetados (ou seja, todos vocês que têm aplicativos que precisam lidar com o tempo que pode ser representado ou originado em locais que praticam o horário de verão), você tem que saber que esse problema existe e levar em conta isso.

Em áreas do mundo que praticam horário de verão, há uma hora a cada outono e primavera onde o tempo aparentemente fica descontrolado. Na noite em que o horário do relógio muda do horário padrão para o horário de verão, o horário salta à frente uma hora. Isso ocorre na primavera. No outono do ano, em uma noite, o relógio horário local salta para trás uma hora.

Nestes dias, você pode encontrar condições em que o dia tem 23 ou 25 horas de duração. Portanto, se você estiver adicionando ou subtraindo intervalos de tempo de valores de data e o intervalo cruzar esse ponto estranho no tempo em que os relógios alternam, seu código precisará fazer um ajuste manual.

Para a lógica que está usando o método DateTime.Parse() para calcular um valor DateTime com base na entrada do usuário de uma data e hora específicas, você precisa detectar que determinados valores não são válidos (no dia de 23 horas) e determinados valores têm dois significados porque uma hora específica se repete (no dia de 25 horas). Para fazer isso, você precisa saber as datas envolvidas e procurar essas horas. Pode ser útil analisar e exibir novamente as informações de data interpretadas à medida que o usuário sai dos campos usados para inserir datas. Como regra, evite que os usuários especifiquem o horário de verão em suas entradas.

Já abordamos a melhor prática para cálculos de intervalo de tempo. Ao converter suas exibições de hora local em tempo universal antes de executar seus cálculos, você supera os problemas de precisão de tempo. O caso mais difícil de gerenciar é o caso de ambiguidade associado à análise da entrada do usuário que ocorre durante essa hora mágica na primavera e no outono.

Atualmente, não há como analisar uma cadeia de caracteres que representa a exibição de tempo de um usuário e atribuí-la com precisão a um valor de tempo universal. A razão é que as pessoas que experimentam horário de verão não vivem em lugares onde o fuso horário é o Horário de Greenwich. Portanto, é inteiramente possível que alguém que mora na costa leste do Estados Unidos digite um valor como "26 de outubro de 2003 01:10:00".

Nesta manhã específica, às 2h, o relógio local é redefinido para 1h, criando um dia de 25 horas. Como todos os valores de hora do relógio entre 1h e 2h ocorrem duas vezes naquela manhã específica, pelo menos na maioria dos Estados Unidos e canadá. O computador realmente não tem como saber qual era a 1:10 da manhã , aquela que ocorre antes do comutador ou aquela que ocorre 10 minutos após o comutador de horário de verão.

Da mesma forma, seus programas têm que lidar com o problema que acontece na primavera quando, em uma manhã específica, não há nenhum horário como 2:10 AM. O motivo é que às 2:00 daquela manhã em particular, a hora nos relógios locais muda repentinamente para 3:00 AM. Toda a 2:00 hora nunca acontece neste dia de 23 horas.

Seus programas precisam lidar com esses casos, possivelmente solicitando ao usuário quando você detecta a ambiguidade. Se você não estiver coletando cadeias de caracteres de data e hora dos usuários e analisando-as, provavelmente não terá esses problemas. Os programas que precisam determinar se uma determinada hora cai no horário de verão podem usar o seguinte:

Timezone.CurrentTimeZone.IsDaylightSavingTime(DateTimeInstance)

ou

DateTimeInstance.IsDaylightSavingTime

Prática recomendada nº 7

Ao testar, se seus programas aceitarem a entrada do usuário especificando valores de data e hora, teste a perda de dados em "spring-ahead", "fall-back" de 23 e 25 horas por dia. Além disso, teste as datas coletadas em um computador em um fuso horário e armazenadas em um computador em outro fuso horário.

Formatação e análise de valores de User-Ready

Para programas que recebem informações de data e hora dos usuários e precisam converter essa entrada de usuário em valores DateTime, a Estrutura dá suporte à análise de cadeias de caracteres formatadas de maneiras específicas. Em geral, os métodos DateTime.Parse e ParseExact são úteis para converter cadeias de caracteres que contêm datas e horas em valores DateTime. Por outro lado, os métodos ToString, ToLongDateString, ToLongTimeString, ToShortDateString e ToShortTimeString são úteis para renderizar valores DateTime em cadeias de caracteres legíveis por humanos.

Dois main problemas que afetam a análise são cultura e cadeia de caracteres de formato. As perguntas frequentes sobre DateTime abordam os problemas básicos em relação à cultura, portanto, aqui vamos nos concentrar nas práticas recomendadas de cadeia de caracteres de formato que afetam a análise de DateTime.

As cadeias de caracteres de formato recomendadas para converter DateTime em cadeias de caracteres são:

'aaaa'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'Z' — Para valores UCT

'aaaa'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'zzz' — Para valores locais

'aaaa'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff' — Para valores de tempo abstratos

Esses são os valores de cadeia de caracteres de formato que seriam passados para o método DateTime.ToString se você quiser obter uma saída compatível com a especificação de tipo DateTime XML. As aspas garantem que as configurações locais de data e hora no computador não substituam as opções de formatação. Se você precisar especificar layouts diferentes, poderá passar outras cadeias de caracteres de formato para uma funcionalidade de renderização de data bastante flexível, mas precisará ter cuidado para usar apenas a notação Z para renderizar cadeias de caracteres de valores UCT e usar a notação zzz para valores de hora local.

A análise de cadeias de caracteres e a conversão em valores DateTime podem ser realizadas com os métodos DateTime.Parse e ParseExact. Para a maioria de nós, Parse é suficiente, pois ParseExact exige que você forneça sua própria instância de objeto Formatador . A análise é bastante capaz e flexível e pode converter com precisão a maioria das cadeias de caracteres que contêm datas e horas.

Por fim, é importante sempre chamar os métodos Parse e ToString somente depois de definir CultureInfo do thread como CultureInfo.InvariantCulture.

Consideração futura

Uma coisa que você não pode fazer facilmente no momento com DateTime.ToString é formatar um valor DateTime em um fuso horário arbitrário. Esse recurso está sendo considerado para implementações futuras do .NET Framework. Se você precisar determinar que a cadeia de caracteres "12:00:00 EST" é equivalente a "11:00:00 EDT", você precisará lidar com a conversão e a comparação por conta própria.

Problemas com o método DateTime.Now()

Há vários problemas ao lidar com o método chamado Now. Para os desenvolvedores do Visual Basic que leem isso, isso também se aplica à função Visual Basic Now . Os desenvolvedores que usam regularmente o método Now sabem que ele é comumente usado para obter a hora atual. O valor retornado pelo método Now está no contexto de fuso horário do computador atual e não pode ser tratado como um valor imutável. Uma prática comum é converter horários que serão armazenados ou enviados entre computadores em tempo Universal (UCT).

Quando o horário de verão é uma possibilidade, há uma prática de codificação que você deve evitar. Considere o seguinte código que pode introduzir um bug difícil de detectar:

Dim timeval As DateTime
timeval = DateTime.Now().ToUniversalTime()  

O valor resultante da execução desse código será desativado em uma hora se for chamado durante a hora extra que ocorre durante o comutador de horário de verão no outono. (Isso só se aplica a computadores que estão em fusos horários que praticam o horário de verão.) Como a hora extra se enquadra nesse local em que o mesmo valor, como 1:10:00, ocorre duas vezes naquela manhã, o valor retornado pode não corresponder ao valor desejado.

Para corrigir isso, uma prática recomendada é chamar DateTime.UtcNow() em vez de chamar DateTime.Now e converter em tempo universal.

Dim timeval As DateTime
timeval = DateTime.UtcNow()  

Esse código sempre terá a perspectiva adequada de 24 horas por dia e, em seguida, poderá ser convertido com segurança na hora local.

Prática recomendada nº 8

Quando você estiver codificando e desejar armazenar a hora atual representada como tempo universal, evite chamar DateTime.Now() seguido por uma conversão em tempo universal. Em vez disso, chame a função DateTime.UtcNow diretamente.

Ressalva: se você pretende serializar uma classe que contém um valor DateTime, certifique-se de que o valor que está sendo serializado não represente o Tempo Universal. A serialização XML não dará suporte à serialização UCT até o lançamento do Whidbey do Visual Studio.

Um par de extras pouco conhecidos

Às vezes, quando você começa a mergulhar em uma parte de uma API, você encontra uma joia oculta — algo que ajuda você a atingir uma meta, mas que, se você não for informado sobre isso, você não descobrirá em suas viagens diárias. O tipo de valor DateTime no .NET tem várias dessas joias que podem ajudá-lo a obter um uso mais consistente do tempo universal.

A primeira é a enumeração DateTimeStyles encontrada no namespace System.Globalization . A enumeração controla os comportamentos das funções DateTime.Parse() e ParseExact que são usadas para converter a entrada especificada pelo usuário e outras formas de representações de cadeia de caracteres de entrada em valores DateTime.

A tabela a seguir realça alguns dos recursos que a enumeração DateTimeStyles habilita.

Constante de enumeração Finalidade Advertências
Adjusttouniversal Quando passado como parte de um método Parse ou ParseExact, esse sinalizador faz com que o valor retornado seja tempo universal. A documentação é ambígua, mas isso funciona com Parse e ParseExact.
Nocurrentdatedefault Suprime a suposição de que as cadeias de caracteres que estão sendo analisadas sem componentes de data terão um valor DateTime retornado que é a hora na data atual. Se essa opção for usada, o valor DateTime retornado será a hora especificada na data gregoriana de 1º de janeiro do ano 1.
AllowWhiteSpaces

Allowtrailingwhite

Allowleadingwhite

AllowInnerWhite

Todas essas opções habilitam a tolerância para espaços em branco adicionados na frente, atrás e no meio das cadeias de caracteres de data que estão sendo analisadas. Nenhum

Outras funções de suporte interessantes são encontradas na classe System.Timezone . Certifique-se de marcar se quiser detectar se o horário de verão afetará um valor datetime ou se você deseja determinar programaticamente o deslocamento de fuso horário atual para o computador local.

Conclusão

A classe .NET Framework DateTime fornece uma interface completa para escrever programas que lidam com o tempo. Entender as nuances de lidar com a classe vai além do que você pode obter do IntelliSense®. Aqui abordamos as práticas recomendadas para codificar e testar programas que lidam com datas e horas. Boa codificação!