API de data do Java: domine o uso de data no seu código

Neste artigo você vai ver:

Lidar com datas ou qualquer operação que usa o tempo como unidade de medida é sempre um grande desafio. Dentro do mundo Java, isso não é diferente, no entanto, desde o Java 8 a nova API de data traz várias melhorias. 

Neste artigo vamos entender mais sobre essa complexidade para trabalhar com datas e como API de data do Java é útil.

Afinal, por que a data é tão complexa?

As APIs de datas são consideradas muito complexas, mas afinal qual o motivo dessa complexidade? Ao contrário das outras unidades de medida, as datas possuem complexidade física, cultural, política, histórica e também religiosa.

A parte física para o tempo já é muito difícil. Albert Einstein em sua teoria da relatividade demonstra que é possível a dilatação do tempo. Desse modo, todas as viagens temporais do filme Interstellar  são comprovadas, é possível entender mais sobre essas teorias no livro “The Science of Interstellar”.

Além da complexidade natural, existem diversas intervenções políticas, como: 

  • Inclusão de dois meses no calendário em homenagem aos imperadores, Júlio Cesar e Caio Otávio Augustos. A consequência disso é que perdemos a semântica: Novembro, do latim novem, representa o mês onze. Dezembro, do latim decem representa o mês doze. Além de deixar fevereiro com bem menos dias, afinal, os meses dos imperadores não podem ter menos que trinta e um dias.
  • O horário de verão foi uma intervenção política, onde o tempo é modificado em um determinado período para economizar energia. Pode ser ativado em um ano ou não, no Brasil, por exemplo, esse recurso não tem sido utilizado nos últimos anos.
  • O dia 30 de dezembro de 2011 não existiu em Samoa. Isso mesmo, um dia inteiro foi eliminado nesse país.

Existem muitos impactos externos ao tempo e isso não ocorre com outras unidades de medida, por exemplo, não existe uma unidade de medida de distância modificada ao longo do ano, como um “metro de verão” ou uma unidade que deixe de existir por decreto.

Entendendo os conceitos do tempo

Já existe uma certa complexidade dentro da API de data, mas também pode-se observar outro problema em grande parte das pessoas que trabalham com desenvolvimento: não entendem o conceito de unidade de tempo. Conhecer esses conceitos já é complexo, sem entender dificulta mais ainda.

Por exemplo, se alguém perguntar: “Que dia é hoje? Ou que horas são?” a resposta será depende, como os grandes problemas na história da computação ou de arquitetura de software. Afinal, para saber o dia ou as horas, depende de qual local estou me referindo, o agora pode mudar se estou me referindo a Lisboa ou a São Francisco.

O objetivo aqui é falar sobre os conceitos mínimos para facilitar o dia a dia de devs:

  • LMT, Local Mean Time: O horário local de uma cidade. Foi uma tentativa de padronizar os horários, porém, como cada cidade tinha o seu próprio horário gerou muita confusão.
  • GMT, Greenwich Mean Time: Uma solução criada por companhias ferroviárias inglesas para facilitar o quadro de horário das viagens. A partir disso, se padronizou os horários dos países com base no horário de Londres, que era definido no observatório mais confiável na época, o Observatório Real de Greenwich. E os horários são relacionados ao GMT, por exemplo, São Francisco está sete horas atrás de Greenwich, GMT-7.
  • UTC, Coordinated Universal time: É como o novo GMT criado em 1972. Podemos pensar no UTC como o GMT 2.0. A unidade de medida dentro do UTC é o offset do qual se pode ter um offset -3:00, isso é três horas do UTC.
  • Time Zone: O padrão GMT/UCT foi implementado no tempo diferente de cada país, é importante ter o histórico antes da implementação, além do horário de verão. O Time Zone tem exatamente esse objetivo: reter esse histórico. Por exemplo, a cidade de São Paulo usava o UTC, -3:06:28, e apenas em 1914 que utilizou o UTC -03:00.
  • Daylight saving time: seu funcionamento é adiantar uma hora, somar um no offset. Isso varia para países e também em qual hemisfério se encontra, por exemplo, no norte acontece entre março e outubro, já no sul é entre novembro e fevereiro. 

Resumindo…

O UTC é o padrão utilizado atualmente para controlar o tempo de todo o mundo e a sua unidade de medida é o offset. Já o horário de verão, se resume em adiantar uma hora do relógio, ou seja, aumentar um no offset do seu horário e o time zone é responsável por conter o histórico dos horários de uma região.

Conhecendo a API de data do Java

Depois da contextualização do negócio e do conceito que está por trás do tempo, vamos falar sobre a não tão nova API de data do Java 8. Existem duas tentativas com tempo no mundo Java, no entanto, a minha recomendação é para os novos sistemas e funcionalidades não importarem nenhuma API que não esteja no pacote “java.time”.

As entidades

O primeiro passo é conhecer os tipos de entidades que a API de data traz suporte, esse artigo não cobrirá o detalhes de todos, mas você pode procurar uma leitura mais detalhada. 

Um ponto importante: a API traz representações que cobrem dia, mês, dia da semana, ano, timezone, offset, além da combinação de todas elas! 

<code=”java”>

DayOfWeek dayOfWeek = DayOfWeek.MONDAY;
Month month = Month.JANUARY;
MonthDay monthDay = MonthDay.now();
YearMonth yearMonth = YearMonth.now();
Year year = Year.now();
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now();
Clock clock = Clock.systemUTC();
Instant instant = Instant.now();
TimeZone timeZone = TimeZone.getDefault();

System.out.println("DayOfWeek: " + dayOfWeek);
System.out.println("month: " + month);
System.out.println("MonthDay: " + monthDay);
System.out.println("YearMonth: " + yearMonth);
System.out.println("Year: " + year);
System.out.println("LocalDate: " + localDate);
System.out.println("LocalTime: " + localTime);
System.out.println("LocalDateTime: " + localDateTime);
System.out.println("OffsetDateTime: " + offsetDateTime);
System.out.println("ZonedDateTime: " + zonedDateTime);
System.out.println("Clock: " + clock.getZone());
System.out.println("Instant: " + instant);
System.out.println("TimeZone: " + timeZone.getDisplayName());
</code>

A primeira interação com a API de data. Nesse memento, o que é feito a criação da cada tipo.

Salientamos aqui as possibilidades que podem ser utilizadas com tipos ou entidades que representam tempo na API, ao invés de usar apenas um único, como era realizado anteriormente. Por exemplo, o YearMonth para trabalhar com a data de validade de um cartão de crédito ou Year para lidar com o ano de publicação de um livro.

Essas variáveis, além de trazer uma melhor semântica para dentro do código, trazem clareza e simplificam como você realiza as validações. Isso é baseado no clássico e já mencionado “When Make a Type” do Martin Fowler. 

<code=”java”>
Assertions.assertThrows(DateTimeException.class, () -> Year.of(1_000_000_000));
</code>

Exemplo da criação da variável Year que lançará uma exceção uma vez que é um ano inválido pela API. O teste foi criado utilizando JUnit 5 ou Jupiter.

É possível também realizar combinações entre tipos para criar uma instância final, por exemplo, começamos com ano e vamos até à data.

<code=”java”>
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
</code>

Criação de modo fluente com relação ao data de aniversário.

Assim, como primeiro passo, sugiro que você explore e leia um pouco mais sobre os tipos que a API suporta. Essas APIs são muito mais eficientes para representar o tempo que tipos mais genéricos como int, long ou String, já que a API de data possui validações temporáveis.

Operações com data

Além de trazer mais semântica e validação para lidar com datas, a API também traz algumas operações bem interessantes que visam facilitar o seu dia, além de salvar o seu tempo. Desculpa o trocadilho tentador. 

As operações mais básicas são as de adicionar ou remover períodos, como mês ou dias, através dos métodos com os sufixos “plus” ou “minus” respectivamente.

<code=”java”>
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate yesterday = myBirthday.minusDays(1);
LocalDate oneYear = myBirthday.plusDays(365);
</code>

Utilizando operações básicas do LocalDate

A interface TemporalAdjuster permite ajustes customizados e algumas operações mais complexas e a partir dela é possível criar várias operações customizáveis e reutilizáveis. Como convenção, essa classe utilitária traz diversos recursos, por exemplo, verificar a próxima segunda-feira a partir da data atual, me refiro a classe TemporalAdjusters.

<code=”java”>
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate newYear = myBirthday.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfMonth = myBirthday.with(TemporalAdjusters.lastDayOfMonth());
LocalDate nextMonday = myBirthday.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
</code>

Exemplo de TemporalAdjustter de maneira simples utilizando a classe utilitária TemporalAdjusters para procurar a próxima segunda-feira.

É possível também realizar comparações entre datas, nada muito recente comparado as API mais antigas, no entanto, é sempre importante salientar que isso existe.

<code=”java”>
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate now = LocalDate.now();
Assertions.assertTrue(now.isAfter(myBirthday));
Assertions.assertFalse(now.isBefore(myBirthday));
</code>

As operações básicas são bem úteis, porém, é possível ir além com o Period e também ChronoUnit. Com eles, você consegue ver a diferença entre um determinado período.

<code=”java”>
LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.now().plusDays(1);
Assertions.assertEquals(1, ChronoUnit.DAYS.between(today, tomorrow));

LocalDate dateA = LocalDate.of(2012, Month.APRIL, 7);
LocalDate dateB = LocalDate.of(2015, Month.DECEMBER,5);

Period period = Period.between(dateA, dateB);

Assertions.assertEquals(3, period.getYears());
Assertions.assertEquals(7, period.getMonths());
Assertions.assertEquals(28, period.getDays());
Assertions.assertEquals(43, period.toTotalMonths());
</code>

Verificando os intervalos de diferença utilizando o Period e também o ChronoUnit.

O último passo para comparação e verificação é o TemporalQuery, que tem o objetivo de extrair alguma informação do tempo.

<code=”java”>
TemporalQuery<Boolean> weekend = temporal -> {
    int dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK);
    return dayOfWeek == DayOfWeek.SATURDAY.getValue()
           || dayOfWeek == DayOfWeek.SUNDAY.getValue();
};
LocalDate date = LocalDate.of(2018, 5, 4);
LocalDateTime sunday = LocalDateTime.of(2018, 5, 6, 17, 0);
Assertions.assertFalse(date.query(weekend));
Assertions.assertTrue(sunday.query(weekend));
</code>

Exemplo do TemporalQuery que extrai uma informação booleana que verifica se é fim de semana ou não.

O ponto de destaque de recursos de operações de data certamente é a possibilidade de trabalhar com os timezones.

<code=”java”>
ZoneId saoPaulo = ZoneId.of("America/Sao_Paulo");
ZonedDateTime adenNow = ZonedDateTime.now(saoPaulo);
ZoneOffset offset = adenNow.getOffset();
Assertions.assertEquals(saoPaulo, adenNow.getZone());
Assertions.assertEquals("-03:00", offset.getId());
</code>

Exemplo de Offset utilizando o timezone de São Paulo.

É possível realizar comparações de timezones diferentes, por exemplo, comparar um horário do Brasil e outro de Portugal, que no dia 3 de maio tem a diferença de quatro horas.

<code=”java”>
ZoneId saoPaulo = ZoneId.of("America/Sao_Paulo");
ZoneId portugal = ZoneId.of("Portugal");
LocalDateTime timeSP = Year.of(2021).atMonth(Month.MAY).atDay(3)
.atTime(12,0,0);

LocalDateTime timeLisbon = Year.of(2021).atMonth(Month.MAY).atDay(3)
.atTime(16,0,0);

ZonedDateTime zoneSaoPaulo = ZonedDateTime.of(timeSP, saoPaulo);
ZonedDateTime zoneLisbon = ZonedDateTime.of(timeLisbon, portugal);
Assertions.assertTrue(zoneSaoPaulo.isEqual(zoneLisbon));
</code>

Comparando timezones de São Paulo e Portugal e achando sua equivalência de três horas no período.

É importante reforçar que o que mencionamos é apenas uma visão geral da API e vale muito ler a documentação do projeto, que é extremamente rica e detalhada.

Conclusão

Antes de concluirmos, que tal se inscrever na newsletter da Zup? Você receberá artigos como esse direto no seu e-mail e ficará por dentro de todos os conteúdos Zup.

Banner com a identidade visual da Zup, nele está escrito Assine nossa Newsletter, os melhores conteúdos sobre carreira e tecnologia no seu e-mail. No final, está um botão com "assinar agora".

Apesar de não ser uma API nova, a API de data do Java ainda é pouco utilizada e pouco explorada por diversos motivos, seja pela complexidade em lidar com data, o número de documentos disponíveis ainda é pouco comparado as APIs legadas ou também pelo simples fato de não ser uma API muito conhecida. 

É importante mencionar que essa API traz muitos benefícios, desde a sua clareza com os métodos a diversos recursos de operação que vale muito a pena conhecer. O objetivo do nosso artigo é apenas reviver esse tópico e trazer o caminho das pedras para você explorar mais a API.

E aí, o que achou da API de data do Java? Conta para a gente nos comentários!

Referências

Capa do artigo sobre API de data do Java onde vemos um calendário sem identificação do mês, mas há tachinhas em algumas datas indicando que há compromissos nelas.
Foto Otávio Santana
Distinguished Software Engineer
Capacitando devs em todo o mundo para fornecer melhores softwares na nuvem.

Artigos relacionados

Capa do artigo em foto com duas pessoas escrevendo códigos em frente a dois notebooks.
Back-End
Postado em:
Imagem capa do conteúdo sobre Framework NestJS com a foto de uma mulher de cabelos trançados de costas de frente para um computador com códigos.
Back-End
Postado em:
Imagem capa do conteúdo sobre testes unitários, onde uma pessoa branca está em pé, segurando um notebook aberto dentro de um data center.
Back-End
Postado em:

Este site utiliza cookies para proporcionar uma experiência de navegação melhor. Consulte nossa Política de Privacidade.