Este artigo foi traduzido por máquina.

O programador

Tempo de Noda

Ted Neward

 

Ted NewardPassei muito tempo pensando sobre o tempo?

Muito cedo na minha carreira, eu estava trabalhando em um sistema que acabou implantando em vários centros de chamadas diferentes. Manter o controle de "quando" um evento ocorreu foi particularmente importante (era um médico -­relacionados com o sistema de call center de enfermeiros), e assim, sem pensar muito sobre isso, nós respeitosamente escreveu o tempo do evento em uma linha de base de dados e deixou-o no que. Exceto, como descobrimos mais tarde, quando o sistema foi implantado em quatro centros de chamadas diferentes, cada uma em um diferente EUA fuso horário, os logs de tempo eram todos um pouco "off", graças ao fato de que nós não tinha pensado para incluir os deslocamentos de fuso horário.

Tempo em um sistema de software é assim, tudo parece muito fácil e simples, até que ele, de repente, não mais.

De acordo com o tema do meus passados duas colunas (todas as minhas colunas podem ser encontradas em bit.ly/ghMsco), mais uma vez a Comunidade .NET beneficia do trabalho realizado pela Comunidade Java ; Neste caso, é um pacote chamado "Tempo de Noda," um Microsoft .NET Framework porta do projeto Java "Joda Time", que em si foi concebida como um substituto para a classe de "Data" de Java (um pedaço horrivelmente quebrado de software remonta aos dias do Java 1.0). Jon Skeet, autor de tempo de Noda, baseado nos algoritmos e conceitos em Joda Time, mas construiu ele do chão como uma biblioteca .NET.

Basta de preâmbulo: Fazer um "pacote de instalação NodaTime" (não aviso nenhum espaço entre "Noda" e "Tempo") e vamos olhar para alguns códigos.

'Me' tempo

A primeira coisa a perceber é que, com o devido respeito às teorias de Einstein, você não tem que estar se aproximando a velocidade da luz para perceber que o tempo é relativo. Se for 19 (que é 1900 para vocês Europeu) aqui em Seattle, então é 19 para todos nós em Seattle, mas é 20 para meus pais em Salt Lake City, 21 para o meu agente de viagens em Dallas e 22 para meu amigo bebendo em Boston. Todos nós que ficamos — que é a magia de fusos horários. Mas meu computador realmente não compreende zonas de tempo, por si só — ele informa o tempo que ele é foi definido, que nesse caso é 19, apesar do fato de que é o exato mesmo momento para todos nós ao redor do mundo. Em outras palavras, não é que o próprio tempo é relativo, é que a nossa representação de tempo é relativa. Em vez de Noda, esta representação é refletida como tempo "global", que significa um momento em um cronograma universal com o qual todos concordam. O que nós consideramos ser tempo de "local" — ou seja, nessa altura com uma zona de tempo associada — tempo Noda chama tempo "zoneada tempo," em oposição a o tempo de Noda considera "local", que é sem qualquer zona de tempo conectada (mais sobre isso posteriormente).

Assim, por exemplo, o tempo Noda "Instantâneas" refere a um ponto na linha do tempo global, com um ponto de origem da meia-noite em Jan. 1, 1970, Coordinated Universal Time (UTC). (Há nada mágico sobre essa data, exceto que este é, por convenção, o "início da época" para sistemas Unix contando "carrapatos" desde essa data e, portanto, serve como bom ponto de origem de referência como qualquer outro.) Então, isso nos dá o instante atual (supondo que o namespace de tempo de Noda é referenciado — "usando NodaTime" — é claro):

var now = SystemClock.Instance.Now;

Para obter um tempo relativo de Seattle, queremos um ZonedDateTime. Isso é essencialmente um instante, mas com as informações de fuso horário incluídas, para que ele se identifica como sendo relativo ao "Seattle, em Jan. 9 2013 "(a data está sendo importante, porque precisamos saber se estamos no horário de Verão [DST] ou não). Um ZonedDateTime é obtida através de um construtor, passando em um instante e uma data­fuso horário. Nós temos o instante, mas precisamos da zona de DateTime para Seattle em DST. Para obter a zona de DateTime, precisamos de um zoneProvider de IDateTime. (A razão para este engano é sutil, mas tem a ver com o fato de que o .NET Framework usa uma representação de zonas de tempo diferentes de qualquer outra plataforma de programação; a Internet Assigned Authority nomes ou IANA, usa um formato como "America/Los_Angeles.") Tempo de Noda oferece dois provedores internos, sendo a versão IANA e o outro o padrão .NET base versão de biblioteca (BCL) de classe, através de propriedades estáticas da classe de zoneProviders de DateTime:

var seattleTZ = dtzi["America/Vancouver"];
var dtzi = DateTime zoneProviders.Tzdb;

A versão de banco de dados (TZDB) fuso horário é o IANA, e assim obter o fuso horário que representa Seattle é uma questão de selecioná-lo (que, de acordo com a IANA, é "America/Los_Angeles," ou se você quiser algo mais perto, "America/Vancouver"):

var seattleNow = new ZonedDateTime(now, seattleTZ);

E se nós imprimir isto para fora, temos uma representação de "Local: 09/01/2013 19:54:16 Deslocamento: -08 Zona: America/Vancouver." Observe que "Offset" da porção na representação? É importante, pois Lembre-se que com base em que dia do ano é (e o que país você está, e o calendar você estiver operando abaixo e...), vai mudar o deslocamento de UTC. Para aqueles de nós em Seattle, a DST significa ganhar ou perder uma hora fora o relógio local, por isso é importante observar o que é o deslocamento de UTC. Na verdade, Noda tempo mantém o controle do que separadamente, porque ao analisar uma data como "2012-06-26T20:41:00 + 01:00," por exemplo, sabemos que foi uma hora antes do UTC, mas não sabemos se isso foi por causa de DST nessa zona de tempo particular ou não.

Tempo ainda acho que é fácil?

'Nós' tempo

Agora vamos supor que eu quero saber quanto tempo até uma data importante na minha vida — como meu 25º aniversário de casamento, que será em Jan. 16, 2018. (Mantendo-se a par de tais coisas é algo importante, como eu acho que qualquer cônjuge iria dizer-lhe, e preciso saber quanto tempo eu tenho antes de eu ter que comprar um presente muito caro). Isto é onde Noda tempo realmente vai brilhar, porque ele vai manter o controle de todos os detalhes pouco mesquinho para você.

Primeiro, preciso construir um LocalDateTime (ou, se eu não me importava sobre o tempo, um LocalDate; ou então, se eu não me importava sobre a data, um LocalTime). Um LocalDateTime (ou LocalDate ou LocalTime) é uma posição relativa da linha do tempo que não sabe exatamente o que é relativo a — em outras palavras, é um ponto no tempo sem saber o seu fuso horário.

(Apesar de não saber o fuso horário, este é ainda um útil bit de informação. Pense nisso como este: Se você e eu estão trabalhando juntos no mesmo escritório, e queremos conhecer mais tarde hoje, eu vou dizer, "Vamos nos encontrar em 16" Porque nós somos ambos no mesmo fuso horário, nenhuma informação adicional é necessária para qualificar este tempo inequivocamente um ao outro.)

Então, porque eu sei o ponto no tempo que eu me importo com ele já, é fácil construir:

var twentyFifth = new LocalDate(2018, 1, 16);

E porque eu só estou perguntando sobre a diferença entre duas datas, sem preocupação com as zonas de tempo, basta-me a parte LocalDate da parte LocalDateTime do ZonedDateTime do meu cálculo anterior:

var today = seattleNow.LocalDateTime.Date;

Mas o que estamos pedindo aqui é para um novo tipo de unidade de tempo: um "período" entre duas vezes. (Na BCL, esta é uma duração). Tempo de Noda usa uma construção diferente para representar isso — um período — e como todos devidamente representadas unidades de medida, requer uma unidade ir com ele. Por exemplo, a resposta "47" é inútil sem uma unidade de acompanhamento, tais como "47 dias," "47 horas" ou "47 anos." Período fornece um método prático, entre, para calcular o número de alguma unidade de tempo determinado entre dois LocalDates:

var period = Period.Between(today, twentyFifth, PeriodUnits.Days);
testContextInstance.WriteLine("Only {0} more days to shop!", period.Days);

Isto diz-me exatamente quantos dias, mas não geralmente contamos dias em que grandes uma quantidade (1.833, no momento que escrevi este artigo) como esse. Nós preferimos a tempo de ser em pedaços mais gerenciáveis, como "anos, meses, dias," que pode novamente pedimos Noda tempo para gerir. Podemos perguntar para dar-em um período que contém uma divisão de anos/meses/dias, por OR-ing, os sinalizadores de PeriodUnits juntos:

Period.Between(today, twentyFifth,
  PeriodUnits.Years | PeriodUnits.Months | PeriodUnits.Days)

Ou, porque este é um pedido muito comum, podemos perguntar-nos dar um período que contém uma divisão de dias/meses/anos usando o sinalizador pré-construídos de mesmo nome:

Period.Between(today, twentyFifth, PeriodUnits.YearMonthDay)

(Aparentemente, ainda tenho um tempinho ainda, que é bom, porque eu não tenho idéia o que para levá-la.)

Tempo de teste

Frequentes leitores desta coluna sabe que eu gosto de escrever testes de exploração, ao investigar uma nova biblioteca, e esta coluna não é excepção. No entanto, é impossível escrever ensaios baseados em tempo, particularmente porque o tempo tem esse hábito irritante de continuando. Cada milésimo de segundo que passa joga fora de qualquer que seja o resultado esperado é, e que torna difícil, se não impossível, para escrever testes que produzirão resultados previsíveis que podemos afirmar e Relatar violações.

Por esta razão, qualquer coisa que fornece tempo (tais como, você sabe, um pulso de disparo) implementa a interface IClock, incluindo o SystemClock que usei anteriormente para obter o instante para "agora" (a propriedade estática agora). Se, por exemplo, criamos uma aplicação que implementa a interface IClock e fornece um valor constante para a propriedade de agora (o único membro exigido pela interface IClock, na verdade), porque o resto a basi de biblioteca de tempo de Noda­camente usa instantes para reconhecer esse momento no tempo, essencialmente, criamos um ambiente inteiramente testável, que permite que toda a coisa a ser testado, afirmado e verificado. Assim, eu posso mudar meu código anterior um pouco e criar um conjunto de testes de exploração, como mostrado no Figura 1.

Figura 1 Criando testes de exploração

[TestClass]
public class UnitTest1
{
  // SystemClock.Instance.Now was at 13578106905161124 when I
  // ran it, so let's mock up a clock that returns that moment
  // in time as "Now"
  public class MockClock : IClock
  {
    public Instant Now
    {
      get { return new Instant(13578106905161124); }
    }
  }
  [TestMethod]
  public void TestMethod1()
  {
    IClock clock = new MockClock(); // was SystemClock.Instance;
    var now = clock.Now;
    Assert.AreEqual(13578106905161124, now.Ticks);
    var dtzi = DateTime zoneProviders.Tzdb;
    var seattleTZ = dtzi["America/Vancouver"];
    Assert.AreEqual("America/Vancouver", seattleTZ.Id);
    var seattleNow = new ZonedDateTime(now, seattleTZ);
    Assert.AreEqual(1, seattleNow.Hour);
    Assert.AreEqual(38, seattleNow.Minute);
    var today = seattleNow.LocalDateTime.Date;
    var twentyFifth = new LocalDate(2018, 1, 16);
    var period = Period.Between(today, twentyFifth, PeriodUnits.Days);
    Assert.AreEqual(1832, period.Days);
  }
}

Passando por todo seu código relacionadas ao tempo e usando o tempo de Noda em vez dos tipos de tempo internos do .NET, código torna-se muito mais testável, simplesmente substituindo o IClock usado para obter o instante para "agora" a algo conhecido e controlável.

Mas espere...

Há muito mais tempo Noda do que apenas o que eu mostrei aqui. Por exemplo, é relativamente fácil de adicionar unidades de tempo (dias, meses e assim por diante) para um determinado momento, usando o "Plus" e "Menos" métodos (para o qual existem também sobrecargas de operador, se os fazem mais sentido usar), assim como uma classe de FakeClock projetado especificamente para testar o código relacionado ao tempo, incluindo a capacidade para programaticamente "Avançar" tempo de alguma forma discreta, tornando mais fácil para testar código decorrido-temporais (como instâncias de fluxo de trabalho do Windows, por exemplo, que deveriam para agir depois de um período de tempo decorrido sem atividade).

Em um nível mais profundo, mais conceitual, Noda tempo demonstra também como um sistema do tipo em uma linguagem de programação pode ajudar a diferenciar entre sutilmente diferentes tipos de valores dentro do domínio do problema: Ao separar os diferentes tipos de tempo (instantes, hora local, locais, datas, locais datas e vezes e zoneadas datas e horários, por exemplo) em tipos distintos e inter-relacionados, que ajuda o programador ser claro e explícito sobre exatamente o que esse código é suposto estar fazendo ou trabalhando com. É particularmente importante, por exemplo, para diferenciar uma data de"nascimento" de um "dia de nascimento" em alguns códigos: Um reflete o momento na linha do tempo do universo, quando a pessoa nasceu, o outro é uma recorrente data em que celebramos esse momento na linha do tempo do universo. (Praticamente falando, um tem um ano ligado a ele, o outro não.)

Skeet deixou claro que ele não considera a biblioteca "acabado" de qualquer maneira, e ele tem planos para melhorar e estendê-lo ainda mais. Felizmente, Noda tempo está disponível para uso hoje, e desenvolvedores devem isso a mesmos a NuGet Noda tempo, dê uma olhada e começam a figurar para fora como e onde usá-lo no domínio do problema. Afinal, o tempo é precioso.

Boa codificação.

Ted Neward -é diretor da Neward & Associates LLC. Ele escreveu mais de 100 artigos e autor e co-autor de uma dúzia de livros, incluindo "profissional F # 2.0" (Wrox, 2010). Ele é um MVP de F# e famoso especialista em Java, além de participar como palestrante sobre o Java e o .NET em conferências no mundo todo. Ele consulta e mentores regularmente — contatá-lo em ted@tedneward.com se você está interessado em ter-lhe vir trabalhar com sua equipe. Ele mantém um blog em blogs.tedneward.com e pode ser seguido no Twitter em twitter.com/tedneward.

Graças ao especialista técnico seguir para revisar este artigo: Jon Skeet
Jon Skeet é um engenheiro de software sênior do Google, trabalhando em Londres. Por dia ele códigos em Java, mas sua paixão é c#. Você pode chegar a Jon no Twitter (@jonskeet) ou simplesmente postar uma pergunta sobre estouro de pilha.