Conheça a nova API de datas do Java 8

Conheça a nova API de datas do Java 8
rferreira
rferreira

Compartilhe

Manipular datas no Java sempre foi algo trabalhoso. No Java 1.0 havia apenas a classe Date, que era complicada de usar e não funcionava bem com internacionalização. Com o lançamento do Java 1.1, surgiu a classe abstrata Calendar, com muito mais recursos, porém com mutabilidade e decisões de design questionáveis.

A partir do Java 8, que foi lançado recentemente, há uma nova API de datas disponível no pacote java.time. Essa API é uma excelente adição às bibliotecas padrão do Java e já vinha sendo desenvolvida desde 2007.

Essa nova API foi baseada na famosa biblioteca JodaTime, que era a salvação para lidar com datas até então. Já falamos sobre como usar o JodaTime para resolver problemas difíceis. A nova API não é exatamente igual ao JodaTime, já que vários detalhes conceituais e de implementação foram melhorados.

Um dos principais conceitos dessa nova API é a separação de como dados temporais são interpretados em duas categorias: a dos computadores e a dos humanos.

Datas para computadores

Para um computador, o tempo é um número que cresce a cada instante. No Java, historicamente era utilizado um long que representava os milissegundos desde 01/01/1970 às 00:00:00. Na nova API, a classe Instant é utilizada para representar esse número, agora com precisão de nanossegundos.

 Instant agora = Instant.now(); System.out.println(agora); //2014-04-08T10:02:52.036Z (formato ISO-8601) 

Podemos usar um Instant, por exemplo, para medir o tempo de execução de um algoritmo.

 Instant inicio = Instant.now(); rodaAlgoritmo(); Instant fim = Instant.now();

Duration duracao = Duration.between(inicio, fim); long duracaoEmMilissegundos = duracao.toMillis(); 

Observe que utilizamos a classe Duration. Essa classe serve para medir uma quantidade de tempo em termos de nanossegundos. Você pode obter essa quantidade de tempo em diversas unidades chamando métodos como toNanos, toMillis, getSeconds, etc.

Datas para humanos

Já para um humano, há uma divisão do tempo em anos, meses, dias, semanas, horas, minutos, segundos e por aí vai. Temos ainda fusos horários, horário de verão e diferentes calendários.

Várias questões surgem ao considerarmos a interpretação humana do tempo. Por exemplo, no calendário judaico, um ano pode ter 13 meses. As classes do pacote java.time permitem que essas interpretações do tempo sejam definidas e manipuladas de forma precisa, ao contrário do que acontecia ao usarmos Date ou Calendar.

Temos, por exemplo, a classe LocalDate que representa uma data, ou seja, um período de 24 horas com dia, mês e ano definidos.

 LocalDate hoje = LocalDate.now(); System.out.println(hoje); //2014-04-08 (formato ISO-8601) 

Um LocalDate serve para representarmos, por exemplo, a data de emissão do nosso RG, em que não nos importa as horas ou minutos, mas o dia todo. Podemos criar um LocalDate para uma data específica utilizando o método of:

 LocalDate emissaoRG = LocalDate.of(2000, 1, 15); 

Note que utilizamos o valor 1 para representar o mês de Janeiro. Poderíamos ter utilizado o enum Month com o valor JANUARY. Há ainda o enum DayOfWeek, que representa os dias da semana.

Para calcularmos a duração entre dois LocalDate, devemos utilizar um Period, que já trata anos bissextos e outros detalhes. ```java LocalDate homemNoEspaco = LocalDate.of(1961, Month.APRIL, 12); LocalDate homemNaLua = LocalDate.of(1969, Month.MAY, 25);

Period periodo = Period.between(homemNoEspaco, homemNaLua);

System.out.printf("%s anos, %s mês e %s dias", periodo.getYears() , periodo.getMonths(), periodo.getDays()); //8 anos, 1 mês e 13 dias


Já a classe `LocalTime` serve para representar apenas um horário, sem data específica. Podemos, por exemplo, usá-la para representar o horário de entrada no trabalho.

```java
 LocalTime horarioDeEntrada = LocalTime.of(9, 0); System.out.println(horarioDeEntrada); //09:00 

A classe LocalDateTime serve para representar uma data e hora específicas. Podemos representar uma data e hora de uma prova importante ou de uma audiência em um tribunal.

 LocalDateTime agora = LocalDateTime.now(); LocalDateTime aberturaDaCopa = LocalDateTime.of(2014, Month.JUNE, 12, 17, 0); System.out.println(aberturaDaCopa); //2014-06-12T17:00 (formato ISO-8601) 

Datas com fuso horário

Para representarmos uma data e hora em um fuso horário específico, devemos utilizar a classe ZonedDateTime. java ZoneId fusoHorarioDeSaoPaulo = ZoneId.of("America/Sao\_Paulo"); ZonedDateTime agoraEmSaoPaulo = ZonedDateTime.now(fusoHorarioDeSaoPaulo); System.out.println(agoraEmSaoPaulo); //2014-04-08T10:02:57.838-03:00America/Sao_Paulo


Com um `ZonedDateTime`, podemos representar, por exemplo, a data de um voo. ```java
 ZoneId fusoHorarioDeSaoPaulo = ZoneId.of("America/Sao\_Paulo"); ZoneId fusoHorarioDeNovaYork = ZoneId.of("America/New\_York");

LocalDateTime saidaDeSaoPauloSemFusoHorario = LocalDateTime.of(2014, Month.APRIL, 4, 22, 30); LocalDateTime chegadaEmNovaYorkSemFusoHorario = LocalDateTime.of(2014, Month.APRIL, 5, 7, 10); ZonedDateTime saidaDeSaoPauloComFusoHorario = ZonedDateTime.of(saidaDeSaoPauloSemFusoHorario, fusoHorarioDeSaoPaulo); System.out.println(saidaDeSaoPauloComFusoHorario); //2014-04-04T22:30-03:00```America/Sao\_Paulo

ZonedDateTime chegadaEmNovaYorkComFusoHorario = ZonedDateTime.of(chegadaEmNovaYorkSemFusoHorario, fusoHorarioDeNovaYork); System.out.println(chegadaEmNovaYorkComFusoHorario); //2014-04-05T07:10-04:00```America/New\_York
 Duration duracaoDoVoo = Duration.between(saidaDeSaoPauloComFusoHorario, chegadaEmNovaYorkComFusoHorario); System.out.println(duracaoDoVoo); //PT9H40M 

Se calcularmos de maneira ingênua a duração do voo, teríamos 8:40. Porém, como há uma diferença entre os fusos horários de São Paulo e Nova York, a duração correta é 9:40. Repare que a API já faz o tratamento de fusos horários distintos.

Outro cuidado importante que devemos ter é em relação ao horário de verão. No fim do horário de verão, por exemplo, a mesma hora existe duas vezes!

 ZoneId fusoHorarioDeSaoPaulo = ZoneId.of("America/Sao\_Paulo");

LocalDateTime fimDoHorarioDeVerao2013SemFusoHorario = LocalDateTime.of(2014, Month.FEBRUARY, 15, 23, 00);

ZonedDateTime fimDoHorarioVerao2013ComFusoHorario = fimDoHorarioDeVerao2013SemFusoHorario.atZone(fusoHorarioDeSaoPaulo); System.out.println(fimDoHorarioVerao2013ComFusoHorario); //2014-02-15T23:00-02:00```America/Sao\_Paulo

ZonedDateTime maisUmaHora = fimDoHorarioVerao2013ComFusoHorario.plusHours(1); System.out.println(maisUmaHora); //2014-02-15T23:00-03:00```America/Sao\_Paulo

Repare no código anterior que, mesmo aumentando uma hora, o horário continuou 23:00. Entretanto, observe que o fuso horário foi alterado de -02:00 para -03:00.

Datas e meses importantes

Existem também as classes MonthDay, que deve ser utilizada para representar datas importantes que se repetem todos os anos, e YearMonth, que deve ser utilizada para representar um mês inteiro de um ano específico. ```java MonthDay natal = MonthDay.of(Month.DECEMBER, 25); YearMonth copaDoMundo2014 = YearMonth.of(2014, Month.JUNE);


### Formatando datas

O `toString` padrão das classes da API utiliza o formato ISO-8601. Se quisermos definir o formato de apresentação da data, devemos utilizar o método `format`, passando um `DateTimeFormatter`.

```java
 LocalDate hoje = LocalDate.now(); DateTimeFormatter formatador = DateTimeFormatter.ofPattern("dd/MM/yyyy"); hoje.format(formatador); //08/04/2014 

O enum FormatStyle possui alguns formatos pré-definidos, que podem ser combinados com um Locale.

 LocalDateTime agora = LocalDateTime.now(); DateTimeFormatter formatador = DateTimeFormatter .ofLocalizedDateTime(FormatStyle.SHORT) .withLocale(new Locale("pt", "br")); agora.format(formatador); //08/04/14 10:02 

Manipulando datas

Todas as classes mencionadas possuem diversos métodos que permitem manipular as medidas de tempo. Por exemplo, podemos usar o método plusDays da classe LocalDate para aumentarmos um dia:

 LocalDate hoje = LocalDate.now(); LocalDate amanha = hoje.plusDays(1); 

Outro cálculo interessante é o número de medidas de tempo até uma determinada data, que podemos fazer através do método until. Para descobrir o número de dias até uma data, por exemplo, devemos passar ChronoUnit.DAYS como parâmetro. ```java MonthDay natal = MonthDay.of(Month.DECEMBER, 25); LocalDate natalDesseAno = natal.atYear(Year.now().getValue()); long diasAteONatal = LocalDate.now() .until(natalDesseAno, ChronoUnit.DAYS);


Podemos utilizar a interface `TemporalAdjuster` para definir diferentes maneiras de manipular as medidas de tempo. É interessante notar que essa é uma interface funcional, permitindo o uso de lambdas.

A classe auxiliar `TemporalAdjusters` já possui diversos métodos que agem como _factories_ para diferentes implementações úteis de `TemporalAdjuster`. Podemos, por exemplo, descobrir qual é a próxima sexta-feira. ```java
 TemporalAdjuster ajustadorParaProximaSexta = TemporalAdjusters.next(DayOfWeek.FRIDAY); LocalDate proximaSexta = LocalDate.now().with(ajustadorParaProximaSexta); 

Imutabilidade e Testabilidade

Se você adicionar um dia a um LocalDate, as informações de data não serão alteradas. ```java LocalDate hoje = LocalDate.now(); //2014-04-08 hoje.plusDays(1); System.out.println(hoje); //2014-04-08 (ainda é hoje, e não amanhã!)


Na verdade, qualquer método que alteraria o objeto retorna uma referência a um novo objeto com as informações alteradas. ```java
 LocalDate hoje = LocalDate.now(); LocalDate amanha = hoje.plusDays(1); boolean mesmoObjeto = hoje == amanha; //false, já que é imutável 

Isso vale para todas as classes do pacote java.time, que são imutáveis e, por isso, são thread-safe e mais fáceis de dar manutenção.

Um outro ponto importante da API é a melhor testabilidade com o uso da classe Clock.

Trabalhando com código legado

Não poderemos mudar todo o nosso código existente para trabalhar com o poder do pacote java.time de uma hora pra outra. Por isso, o Java 8 trouxe alguns pontos de interoperabilidade entre os antigos Date e Calendar e a nova API. ```java Calendar calendar = Calendar.getInstance(); Instant instantAPartirDoCalendar = calendar.toInstant(); Date dateAPartirDoInstant = Date.from(instantAPartirDoCalendar); Instant instantAPartirDaDate = dateAPartirDoInstant.toInstant();


Além disso, classe abstrata `Calendar` ganhou um _builder_, que possibilita a criação de uma instância de maneira fluente. ```java
 Calendar calendario = new Calendar.Builder() .setDate(2014, Calendar.APRIL, 8) .setTimeOfDay(10, 2, 57) .setTimeZone(TimeZone.getTimeZone("America/Sao\_Paulo")) .setLocale(new Locale("pt", "br")) .build(); 

A nova API de datas do Java 8 é bastante extensa, possuindo diversos outros recursos interessantes. Com certeza, é uma adição muito bem-vinda ao Java, que vai facilitar bastante o trabalho de manipulação de datas.

Veja outros artigos sobre Programação