13 maio 2016

Nova API de datas em Java 8

Na linguagem Java sempre foi trabalhoso a manipulação de datas, e devido a varias formas de manipulação cada aplicação era feito de forma diferente. A partir do Java 1.1 a classe Calendar surgiu como solução, com muito mais recursos, porém com mutabilidade e decisões de design questionáveis.Dentre várias formas de manipulação como Date do java.ultil, a Java nessa versão pegou como base uma Lib que mais é usada e mais facil de usar , JodaTime e dentro da java.ultil aplicou o essa nova versão com varias melhorias.

Tendo como base duas formas de pensar em tempo uma da forma de computação e outra forma humana.

Datas em computadores

Nessa nova API com base no tempo de um numero crescente como se fazia nos milissegundos, a classe Instant substitui o Long e tem a precisão de nanossegundos:

1
2
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.

1
2
3
4
5
6
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

Porem para nós humanos, nós vemos o tempo de forma diferente, não em números mais em dias,meses anos,horários de verão, fuso horários, etc.

Alem desse ponto podemos também observar que existem outros calendários, como judaico que tem 13 meses, e com o pacote java.time temos como manipular de forma precisa, coisa que não tinhamos com o Date ou Calendar, como no exemplo temos a classe Local.Date que representa um período da data atual:

1
2
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:

1
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.

1
2
3
4
5
6
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.

1
2
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.

1
2
3
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)

Na parte que trata os fusos ultilizamos a classe classe ZonedDateTime, onde definimos a origem que aquela data tem que ser manipulada.

1
2
3
ZoneId fusoHorarioDeSaoPaulo = ZoneId.of("America/Sao_Paulo");
ZonedDateTime agoraEmSaoPaulo = ZonedDateTime.now(fusoHorarioDeSaoPaulo);
System.out.println(agoraEmSaoPaulo); //2014-04-08T10:02:57.838-03:00[America/Sao_Paulo

Com um ZonedDateTime, podemos representar, por exemplo, a data de um voo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

Sem ter a relevancia do fuso teriamos um voo de 8:40, mais com a API teremos os fusos considerados e teremos um voo de 9:40.

Outro ponto comum que tinhamos problemas era com o horario de verão onde o dia era contado duas vezes e com a API nova não temos mais esse problema.

1
2
3
4
5
6
7
8
9
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.

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.

1
2
MonthDay natal = MonthDay.of(Month.DECEMBER, 25);
YearMonth copaDoMundo2014 = YearMonth.of(2014, Month.JUNE);

Com o toString podemos definir o formato da data para vizualizar de acordo com a necessidade.

1
2
3
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.

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

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:

1
2
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.

1
2
3
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.

1
2
TemporalAdjuster ajustadorParaProximaSexta = TemporalAdjusters.next(DayOfWeek.FRIDAY);
LocalDate proximaSexta = LocalDate.now().with(ajustadorParaProximaSexta);

Qualquer método que alteraria o objeto retorna uma referência a um novo objeto com as informações alteradas.

1
2
3
LocalDate hoje = LocalDate.now(); //2014-04-08
hoje.plusDays(1);
System.out.println(hoje); //2014-04-08 (ainda é hoje, e não amanhã!)

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.

Trabalhando com código legado

O Java 8 trouxe alguns metodos para ter interações com codigos legados do Date e Calendar.

1
2
3
4
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.

1
2
3
4
5
6
7
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();

Assim podemos ultilizar os novos recursos dessa API que com certeza vai facilitar bastante o nosso trabalho.

CTA-ebook-transformação-digital

Leave a Comment