[chapter Chega de Calendar! Nova API de datas] chapter Enough with Calendar! New date API Trabalhar com datas em Java sempre foi um tanto complicado. Existem muitas críticas às classes %%Date%% e %%Calendar%%: são mutáveis, possuem várias limitações, decisões de design estranhas, alguns bugs conhecidos e dificuldade de se trabalhar com elas. Working with dates in Java has always been somewhat complicated. There are many criticisms in regards to the %%Date%% and %%Calendar%% classes: they are changeable, have several limitations, have strange design decisions, some known bugs and it is difficult to work with them. [index Date] [index Calendar] Uma grande introdução do Java 8 foi o pacote %%java.time%%, que nos trouxe uma nova API de datas! A great Java 8 introduction is the %%java.time%% package, which brings us new date API! [section A java.time vem do Joda Time] section Java.time comes from Joda Time [index Joda time] Há muito tempo existe o desejo de uma API de datas melhor, baseada no projeto Joda-Time. Essa afirmação foi uma das motivações usadas na proposta dessa nova feature. There has long been a desire for a better date API based on the Joda-Time project. This statement was one of the motivations used in the proposal of this new feature. O Joda-Time é uma poderosa biblioteca open source bastante conhecida e utilizada no mercado, que trabalha com tempo, datas e cronologia: Joda-Time is a powerful open source library well known and used in the market that works with time, dates and chronology: http://www.joda.org/joda-time/ Baseada nesse projeto, a nova API de datas possui métodos mais intuitivos, seu código ficou muito mais interessante e fluente. Você pode conhecer mais da JSR 310, que criou essa API, aqui: Based on this project, the new API dates have more intuitive methods -- its code is much more interesting and fluent. You can see more JSR 310, which created this API here: [index especificação] index specification https://jcp.org/en/jsr/detail?id=310. Ela foi especificada por Steven Colebourne e Michael Nascimento, dois nomes bem conhecidos na comunidade, sendo este último um brasileiro. It was specified by Steven Colebourne and Michael Nascimento, two well known names in the community, the latter being a Brazilian. [section Trabalhando com datas de forma fluente] section Working with dates fluently Aplicar uma transformação em um %%Calendar%% é um processo muito verboso, como por exemplo para criar uma data com um mês a partir da data atual. Repare: Applying a transformation on a %%Calendar%% is a very wordy process. For example, to create a date one month from the current date, you have to do this: [code java] Calendar nextMonth = Calendar.getInstance(); nextMonth.add(Calendar.MONTH, 1); [/code] Com a nova API de datas podemos fazer essa mesma operação de uma forma mais moderna, utilizando sua interface fluente: With the new date API we can do the same operation in a more modern way using its fluent interface: [index LocalDate] [code java] LocalDate nextMonth = LocalDate.now().plusMonths(1); [/code] Como a maior parte de seus métodos não possibilita o retorno de parâmetros nulos, podemos encadear suas chamadas de forma fluente sem a preocupação de receber um %%NullPointerException%%! Since most of its methods doesn't allow returning null parameters, we can chain its calls fluently without worrying about receiving a %%NullPointerException%%! Da mesma forma que fizemos para adicionar o mês, podemos utilizar os métodos %%plusDays%%, %%plusYears%% em diante de acordo com nossa necessidade. E de forma igualmente simples, conseguimos decrementar os valores utilizando os métodos %%minus%% presentes nesses novos modelos. Para subtrair um ano, por exemplo, faríamos: Just like we did when we added the month, we can use the %%plusDays%% and %%plusYears%% methods according to our need. And we could as easily decrement values ​​using the %%minus%% methods present in these new models. To subtract one year, for example, we would do: [code java] LocalDate lastYear = LocalDate.now().minusYears(1); [/code] [index LocalDateTime] Um ponto importante é que a classe %%LocalDate%% representa uma data sem horário nem timezone, por exemplo %%01-25-2014%%. Se as informações de horário forem importantes, usamos a classe %%LocalDateTime%% de forma bem parecida: An important point to remember is that the %%LocalDate%% class represents a date without time or timezone, eg %%01-25-2014%%. If the time information is important, we use the %%LocalDateTime%% class in much the same way: [code java] LocalDateTime now = LocalDateTime.now(); System.out.println(now); [/code] Um exemplo de saída desse código seria %%2014-02-19 T17:49:19.587%%. An output sample of this code would be %%2014-02-19 T17:49:19.587%%. Há ainda o %%LocalTime%% para representar apenas as horas: There is also %%LocalTime%% to represent only the hours: [code java] LocalTime now = LocalTime.now(); [/code] Neste caso, a saída seria algo como %%17:49:19.587%%. In this case, the output would be something like %%17:49:19.587%%. Uma outra forma de criar uma %%LocalDateTime%% com horário específico é utilizando o método %%atTime%% da classe %%LocalDate%%, repare: Another way to create a %%LocalDateTime%% with a specific time is using the %%atTime%% method from the %%LocalDate%% class. Like this: [code java] LocalDateTime todayAtMidday = LocalDate.now().atTime(12,0); [/code] Assim como fizemos com esse método %%atTime%%, sempre podemos utilizar os métodos %%at%% para combinar os diferentes modelos. Observe como fica simples unir uma classe %%LocalDate%% com um %%LocalTime%%: Just like we did with the %%atTime%% method, we can always use the %%at%% method*s* to combine the different models. Notice how simple it is to join a %%LocalDate%% class with a %%LocalTime%%: [code java] LocalTime now = LocalTime.now(); LocalDate today = LocalDate.now(); LocalDateTime dateAndTime = today.atTime(now); [/code] [index ZonedDateTime] Também podemos, a partir desse %%LocalDateTime%%, chamar o método %%atZone%% para construir um %%ZonedDateTime%%, que é o modelo utilizado para representar uma data com hora e timezone. We can also, with %%LocalDateTime%%, call the %%atZone%% method to build a %%ZonedDateTime%%, which is the model used to represent a date with time and timezone. [code java] ZonedDateTime dateTimeAndTimezone = dateAndTime.atZone(ZoneId.of("America/Los_Angeles")); [/code] Em alguns momentos é importante trabalhar com o timezone, mas o ideal é utilizá-lo apenas quando realmente for necessário. A própria documentação pede cuidado com o uso dessa informação, pois muitas vezes não será necessário e usá-la pode causar problemas, como para guardar o aniversário de um usuário. Sometimes it's important to work with timezone, but ideally you should use it only when absolutely necessary. The documentation itself warns you to be careful when using this information, you often don't need to and using it can cause problems, like when you save a user's birthday. Para converter esses objetos para outras medidas de tempo podemos utilizar os métodos %%to%%, como é o caso do %%toLocalDateTime%% presente na classe %%ZonedDateTime%%: To convert these objects to other time zones, we can use the %%to%% methods, as in the case of %%toLocalDateTime%% present in the %%ZonedDateTime%% class: [code java] LocalDateTime withoutTimeZone = dateTimeAndTimezone.toLocalDateTime(); [/code] O mesmo pode ser feito com o método %%toLocalDate%% da classe %%LocalDateTime%%, entre diversos outros métodos para conversão. The same can be done with the %%toLocalDate%% method from the %%LocalDateTime%% class, among many other methods for conversion. Além disso, as classes dessa nova API contam com o método estático %%of%%, que é um ::factory method:: para construção de suas novas instâncias: Moreover, the classes of this new API have the static method %%of%%, which is a ::factory method:: for the construction of its new instances: [code java] LocalDate date = LocalDate.of(2014, 12, 25); LocalDateTime dateTime = LocalDateTime.of(2014, 12, 25, 10, 30); [/code] E claro, é muito comum precisarmos instanciar uma data a partir de uma %%String%%. Para isso, podemos utilizar o método %%parse%% que será melhor detalhado adiante. Of course, we often need to instantiate a date from a %%String%%. To do this, we use the %%parse%% method which we will see in more details later. [index imutabilidade] index immutability Repare que todas essas invocações não só podem como devem ser encadeadas por um bom motivo: o modelo do %%java.time%% é imutável! Cada operação devolve um novo valor, nunca alterando o valor interno dos horários, datas e intervalos utilizados na operação. Isso simplifica muita coisa, não apenas para trabalhar concorrentemente. Note that all these invocations not only can, but must be chained for a reason: the %%java.time%% model is immutable! Each operation returns a new value and never changes the internal value of the times, dates and intervals used in the operation. This simplifies things a lot, and not only when working concurrently. De forma similar aos %%setter%%s, os novos modelos imutáveis possuem os métodos %%with%%s para facilitar a inserção de suas informações. Para modificar o ano de um %%LocalDate%%, por exemplo, poderíamos utilizar o método %%withYear%%. Repare: Similarly to the %%setter%%s, the new immutable models have the %%with%%s methods to facilitate the insertion of information. To modify a year of a %%LocalDate%%, for example, we could use the %%withYear%% method. Like this: [code java] LocalDate dataDoPassado = LocalDate.now().withYear(1988); [/code] Mas e para recuperar essas informações? Podemos utilizar seus métodos %%get%%s, de acordo com o valor que estamos procurando. Por exemplo, o %%getYear%% para saber o ano, ou %%getMonth%% para o mês, assim por diante. And and how to retrieve this information? We can use its %%get%%s methods according to the value we're looking for. For example, %%getYear%% to know the year, or %%getMonth%% for the month, and so on. [code java] LocalDate longTimeAgo = LocalDate.now().withYear(1988); System.out.println(longTimeAgo.getYear()); [/code] Neste caso, a saída será 1988, que foi o valor adicionado pelo método %%withYear%%. In this case, the output will be 1988 which was the value added by the %%withYear%% method. Isso simplifica um pouco nosso código. Em um %%Calendar%%, por exemplo, teríamos que utilizar o método %%get%% passando a constante %%Calendar.YEAR%% como argumento. This simplifies our code a bit. In a %%Calendar%%, for example, we would have to use the %%get%% method and pass the %%Calendar.YEAR%% constant as argument. Existem também outras funcionalidades úteis, como saber se alguma medida de tempo acontece antes, depois ou ao mesmo tempo que outra. Para esses casos, utilizamos os métodos %%is%%: There are also some other useful features, like knowing if some time measure happens before, after or at the same time as another. In these cases, the %%is%% methods are used: [code java] LocalDate today = LocalDate.now(); LocalDate tomorrow = LocalDate.now().plusDays(1); System.out.println(today.isBefore(tomorrow)); System.out.println(today.isAfter(tomorrow)); System.out.println(today.isEqual(tomorrow)); [/code] Neste exemplo, apenas o %%isBefore%% vai retornar %%true%%. In this example, only %%isBefore%% will return %%true%%. Há ainda os casos em que queremos comparar datas iguais, porém em %%timezones%% diferentes. Utilizar o método %%equals%%, nesse caso, não causaria o efeito esperado — claro, afinal a sobrescrita desse método na classe %%ZonedDateTime%% espera que o %%offset%% entre as datas seja o mesmo: There are still cases where we want to compare the same dates, but in different %%timezones%%. Using the %%equals%% method in this case would not give the expected effect since the method overwrite in the %%ZonedDateTime%% class expects that the %%offset%% between the dates is the same: [code java] @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof ZonedDateTime) { ZonedDateTime other = (ZonedDateTime) obj; return dateTime.equals(other.dateTime) && offset.equals(other.offset) && zone.equals(other.zone); } return false; } [/code] Para estes casos podemos e devemos utilizar o método %%isEqual%% exatamente como fizemos com o %%LocalDate%%. Repare: For these cases we can and should use the %%isEqual%% method just as we did with %%LocalDate%%. Such like this: [code java] ZonedDateTime tokyo = ZonedDateTime .of(2011, 5, 2, 10, 30, 0, 0, ZoneId.of("Asia/Tokyo")); ZonedDateTime losAngeles = ZonedDateTime .of(2011, 5, 2, 10, 30, 0, 0, ZoneId.of("America/Los_Angeles")); System.out.println(tokyo.isEqual(losAngeles)); [/code] Não muito diferente do que podemos esperar, o resultado ainda será %%false%%. Apesar da semelhança entre as datas, elas estão em timezones diferentes, portanto não são iguais. Para que o resultado do método %%isEqual%% de um %%ZonedDateTime%% seja %%true%%, precisamos acertar a diferença de tempo entre as duas datas. Uma forma de fazer isso seria adicionar as 16 horas de diferença na instância de %%tokyo%%, repare: As expected, the result will still be %%false%%. Despite the similarity between the dates, they are in different timezones, so they are not equal. In order for the result from the %%isEqual%% method of a %%ZonedDateTime%% to return %%true%%, we need to set the time difference between two dates. One way to do this would be to add the 16 hours difference in %%tokyo%%'s instance. Like this: [code java] ZonedDateTime tokyo = ZonedDateTime .of(2011, 5, 2, 10, 30, 0, 0, ZoneId.of("Asia/Tokyo")); ZonedDateTime losAngeles = ZonedDateTime .of(2011, 5, 2, 10, 30, 0, 0, ZoneId.of("America/Los_Angeles")); tokyo = tokyo.plusHours(16); System.out.println(tokyo.isEqual(losAngeles)); [/code] Agora sim, ao executar o método a saída será %%true%%. Now indeed, when running the method, the output will be %%true%%. A nova API possui diversos outros modelos que facilitam bastante nosso trabalho com data e tempo, como por exemplo as classes %%MonthDay%%, %%Year%% e %%YearMonth%%. The new API has several other models that facilitate enough our work with date and time, such like the %%MonthDay%%, %%Year%% and %%YearMonth%% classes. Para obter o dia do mês atual, por exemplo, poderíamos utilizar o método %%getDayOfMonth%% de uma instância da classe %%MonthDay%%. Repare: For the day of the current month, for example, we could use the %%getDayOfMonth%% method from an instance of the %%MonthDay%% class. Like this: [code java] System.out.println("The current day of month is: "+ MonthDay.now().getDayOfMonth()); [/code] Você pode pegar o %%YearMonth%% de uma determinada data. You can take the %%YearMonth%% of a specific date. [code java] YearMonth ym = YearMonth.from(date); System.out.println(ym.getMonth() + " " + ym.getYear()); [/code] A vantagem de trabalhar apenas com ano e mês, por exemplo, é poder agrupar dados de uma forma mais direta. Com o %%Calendar%%, precisaríamos utilizar uma data completa e ignorar dia e hora, por exemplo. Soluções incompletas. The advantage of working with only the year and the month, for example, is that you can group dates in a more direct way. For example, with the %%Calendar%%, we would need to use a complete date and ignore the day and hour. An incomplete solution. [section Enums no lugar de constantes] section Enums instead of constants Essa nova API de datas favorece o uso de Enums no lugar das famosas constantes do %%Calendar%%. Para representar um mês, por exemplo, podemos utilizar o enum %%Month%%. Cada valor desse enum tem um valor inteiro que o representa seguindo o intervalo de 1 (Janeiro) a 12 (Dezembro). Você não precisa, mas trabalhar com esses enums é uma boa prática, afinal seu código fica muito mais legível: These new date API favor the use of Enums instead of the %%Calendar%%'s famous constants. To represent a month, for example, we can use the %%Month%% enum. Each value of this Enum has an integer value that represents it, according to the range 1 (January) to 12 (December). You don't need to, but working with these Enums is a good habit because it makes your code much more readable: [code java] System.out.println(LocalDate.of(2014, 12, 25)); System.out.println(LocalDate.of(2014, Month.DECEMBER, 25)); [/code] Nos dois casos, o valor de saída é 2014/12/25. In both cases, the output value is 2014/12/25. Outra vantagem de utilizar os enums são seus diversos métodos auxiliares. Note como é simples consultar o primeiro dia do trimestre de determinado mês, ou então incrementar/decrementar meses: Another advantage of using Enums are their many helper methods. Note how simple it is to query the first day of the quarter containing a specific month, or to increment/decrement months: [code java] System.out.println(Month.DECEMBER.firstMonthOfQuarter()); System.out.println(Month.DECEMBER.plus(2)); System.out.println(Month.DECEMBER.minus(1)); [/code] O resultado desse código será: This code's result will be: [code] OCTOBER FEBRUARY NOVEMBER [/code] Para imprimir o nome de um mês formatado, podemos utilizar o método %%getDisplayName%% fornecendo o estilo de formatação (completo, resumido, entre outros) e também o %%Locale%%: To print the name of a month in a specific format, we can use the %%getDisplayName%% method providing the formatting style (full or summarized among others) and also the %%Locale%%: [code java] Locale us = Locale.US; System.out.println(Month.DECEMBER .getDisplayName(TextStyle.FULL, us)); System.out.println(Month.DECEMBER .getDisplayName(TextStyle.SHORT, us)); [/code] Repare que, como estamos utilizando %%TextStyle.FULL%% no primeiro exemplo e %%TextStyle.SHORT%% no seguinte, teremos como resultado: Notice that, since we are using %%TextStyle.FULL%% in the first example and %%TextStyle.SHORT%% in the following, our result will be: [code] December Dec [/code] Outro enum introduzido no %%java.time%% foi o %%DayOfWeek%%. Com ele, podemos representar facilmente um dia da semana, sem utilizar constantes ou números mágicos! Another Enum introduced in %%java.time%% was the %%DayOfWeek%%. With it, we can easily represent a day of the week without using constants or magic numbers! [section Formatando com a nova API de datas] section Formatting in the new date API [index formatters] A formatação de datas também recebeu melhorias. Formatar um %%LocalDateTime%%, por exemplo, é um processo bem simples! Tudo que você precisa fazer é chamar o método %%format%% passando um %%DateTimeFormatter%% como parâmetro. The formatting of dates also improved. Formatting a %%LocalDateTime%%, for example, is a very simple process! All you need to do is call the %%format%% method passing a %%DateTimeFormatter%% as a parameter. Para formatar em horas, por exemplo, podemos fazer algo como: To format in hours, for example, we can do something like: [code java] LocalDateTime now = LocalDateTime.now(); String result = now.format(DateTimeFormatter.ISO_LOCAL_TIME); [/code] Um exemplo de resultado seria %%01:15:45%%, ou seja, o pattern é %%hh:mm:ss%%. An example of a result would be %%01:15:45%%, i.e. the pattern is %%hh:mm:ss%%. Note que usamos um %%DateTimeFormatter%% predefinido, o %%ISO_LOCAL_TIME%%. Assim como ele existem diversos outros que você pode ver no javadoc do %%DateTimeFormatter%%. Note that we use an existing %%DateTimeFormatter%%, %%ISO_LOCAL_TIME%%. There are many others like this one you can see on %%DateTimeFormatter%% javadoc. Mas como criar um %%DateTimeFormatter%% com um novo padrão? Uma das formas é usando o método %%ofPattern%%, que recebe uma %%String%% como parâmetro: But how to create a %%DateTimeFormatter%% with a new pattern? One way is by using the %%ofPattern%% method, which receives a %%String%% as parameter: [code java] LocalDateTime now = LocalDateTime.now(); now.format(DateTimeFormatter.ofPattern("MM-dd-yyyy")); [/code] O resultado do método %%format%% seria, por exemplo, 02-06-2014. Também existe uma sobrecarga desse método que, além do ::pattern::, recebe um %%java.util.Locale%%. The result of the %%format%% method would be, for example, 02-06-2014. There is also an overload of this method which, beyond the ::pattern::, receives a %%java.util.Locale%%. De forma parecida, podemos transformar uma %%String%% em alguma representação de data ou tempo válida, e para isso utilizamos o método %%parse%%! Podemos, por exemplo, retornar o nosso resultado anterior em um %%LocalDate%% utilizando o mesmo formatador: Similarly, we can transform a %%String%% in some representation of valid date or time, and for this we use the %%parse%% method! We may, for example, return our previous result in a %%LocalDate%% using the same formatter: [code java] LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd-yyyy"); String result = now.format(formatter); LocalDate parsed = LocalDate.parse(result, formatter); [/code] Repare que fizemos o ::parse:: desse resultado em um %%LocalDate%%, e não em um %%LocalDateTime%%, que é o tipo da data inicial. Não poderíamos retornar um %%LocalDateTime%%, afinal quando formatamos em data (com a padrão %%MM-dd-yyyy%%), perdemos as informações de tempo! Essa tentativa resultaria em uma %%DateTimeParseException%%! Notice that we attributed the results of ::parse:: to a %%LocalDate%%, and not in a %%LocalDateTime%%, which was our start date type. We could not return a %%LocalDateTime%% because, when we formatted the date (with the pattern %%MM-dd-yyyy%%), we lost the time information! This attempt would result in a %%DateTimeParseException%%! [section Datas inválidas] section Invalid dates Ao trabalhar com %%Calendar%%, alguma vez você já pode ter se surpreendido ao executar um código como este: When you work with %%Calendar%%, sometimes you can be surprised when running a code like this: [code java] Calendar instante = Calendar.getInstance(); instante.set(2014, Calendar.FEBRUARY, 30); SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy"); System.out.println(dateFormat.format(instante.getTime())); [/code] Levando em consideração que fevereiro deste ano só vai até o dia 28, qual será o resultado? Uma exception? Esse pode ser o resultado esperado, afinal estamos passando um argumento inválido! Considering that February this year only has 28 days, what will be the result? An exception? This result can be expected. After all we are passing an invalid argument! Mas, na verdade, o resultado será: 02/03/14. Isso mesmo, o %%Calendar%% ajustou o mês e dia, sem dar nenhum feedback desse erro que provavelmente passaria despercebido. In fact, the result will be: 02/03/14. That's right, %%Calendar%% set the month and day without giving any feedback about this error, it would probably go unnoticed. Nem sempre isso é o que esperamos ao tentar criar uma data com valor inválido. This is not always what we expect when trying to create a date with an invalid value. Muito diferente desse comportamento, a nova API de datas vai lançar uma %%DateTimeException%% em casos como esse. Repare: The new dte API don't behave this way. They throw a %%DateTimeException%% in cases like this: [code java] LocalDate.of(2014, Month.FEBRUARY, 30); [/code] O resultado será: %%java.time.DateTimeException: Invalid date 'FEBRUARY 30'%%. The result will be %java.time.DateTimeException: Invalid date 'FEBRUARY 30'%%. O mesmo acontecerá se eu tentar criar um %%LocalDateTime%% com um horário inválido: The same thing will happen if you try to create a %%LocalDateTime%% with an invalid time: [code java] LocalDateTime invalidHour = LocalDate.now().atTime(25, 0); [/code] Neste caso, a exception será: %%java.time.DateTimeException: Invalid value for HourOfDay (valid values 0 - 23): 25%%. In this case, the exception will be: %%java.time.DateTimeException: Invalid value for HourOfDay (valid values 0 - 23): 25%%. A própria API do Joda-time deixou passar um problema parecido ao criar a classe %%DateMidnight%%. Na documentação do método %%toDateMidnight%% da classe %%LocalDate%%, você vai encontrar a seguinte nota: The Joda-time API itself left a similar problem when creating the %%DateMidnight%% class. In the %%toDateMidnight%% method's documentation from the %%LocalDate%% class, you will find the following note: "::Desde a versão 1.5, recomendamos que você evite o %%DateMidnight%% e passe a utilizar o método %%toDateTimeAtStartOfDay%% por causa da exceção que será detalhada a seguir. Esse método vai lançar uma exceção se o horário de verão padrão começar à meia noite e esse %%LocalDate%% represente esse horário da troca. O problema é que não existe meia noite nessa data e, assim, uma exceção é lançada::". "::As from v1.5, you are recommended to avoid DateMidnight and use toDateTimeAtStartOfDay() instead because of the exception detailed below. This method will throw an exception if the default time zone switches to Daylight Savings Time at midnight and this LocalDate represents that switchover date. The problem is that there is no such time as midnight on the required date, and as such an exception is thrown.::" Ou seja, esse método é um tanto perigoso, pois sempre lança uma exceção no dia em que começa o horário de verão. O próprio Stephen Colebourne disse em seu blog que não existe uma boa razão para algum dia utilizar a classe %%DateMidnight%%. E claro, essa classe não está presente na nova API de datas. This means that this method is somewhat dangerous, since it always throws an exception the day the DST starts. Stephen Colebourne himself said in his blog that there is no good reason to ever use the %%DateMidnight%% class. Of course, this class isn't present in the new date API. [section Duração e Período] section Duration and Period Sempre foi problemático trabalhar com a diferença de alguma medida de tempo em Java. Por exemplo, como calcular a diferença de dias entre dois %%Calendar%%s? Você possivelmente já passou por isso: It has always been difficult to work with the difference between time measurements in Java. For example, how can we calculate the difference of days between two %%Calendar%%s? You probably did this: [code java] Calendar calendar = Calendar.getInstance(); Calendar anotherCalendar = Calendar.getInstance(); anotherCalendar.set(1988, Calendar.JANUARY, 25); long diff = calendar.getTimeInMillis() - anotherCalendar.getTimeInMillis(); long dayMilliseconds = 1000 * 60 * 60 * 24; long days = diff / dayMilliseconds; [/code] Resolvemos o problema, claro, mas sabemos que trabalhar com a diferença de milissegundos pode não ser o ideal. Há também alguns casos particulares que não são cobertos nesse caso. E a cada nova necessidade, precisaríamos criar um código igualmente verboso e difícil de manter. Yes, we solved the problem, but we know that working with the difference of milliseconds isn't ideal. There are also some special cases that are not covered in this case. And for every new need, we need to create an equally verbose and difficult to maintain code. [index ChronoUnit] Agora podemos fazer essa mesma operação de forma muito mais simples, utilizando o enum %%ChronoUnit%% da nova api: We can now do the same thing in a much simpler way using the new API's %%ChronoUnit%% Enum: [code java] LocalDate now = LocalDate.now(); LocalDate anotherDate = LocalDate.of(1989, Month.JANUARY, 25); long days = ChronoUnit.DAYS.between(anotherDate, now); [/code] Esse enum está presente no pacote %%java.time.temporal%% e possui uma representação para cada uma das diferentes medidas de tempo e data. Além disso, possui vários métodos para facilitar o cálculo de diferença entre seus valores e que nos auxiliam a extrair informações úteis, como é o caso do %%between%%. This Enum is present in the %%java.time.temporal%% package and has a representation for each of the different measures of time and date. Moreover, it has various methods to facilitate the calculation of difference between its values ​​and assist in extracting useful information, as is the case with %%between%%. Mas e se também quisermos saber a diferença de anos e meses entre essas duas datas? Poderíamos utilizar o %%ChronoUnit.YEARS%% e %%ChronoUnit.MONTHS%% para obter essas informações, mas ele vai calcular cada uma das medidas de forma separada. Repare: But what if we also want to know the difference in years and months between those two dates? We could use %%ChronoUnit.YEARS%% and %%ChronoUnit.MONTHS%% to get this information, but it will calculate each measures separately. Like this: [code java] long months = ChronoUnit.MONTHS.between(anotherDate, now4); long years = ChronoUnit.YEARS.between(anotherDate, now4); System.out.printf("%s days, %s months and %s years", days2, months, years); [/code] Neste caso, o resultado será algo como: 9147 days, 300 months and 25 years. In this case, the result will be something like: 9147 days, 300 months and 25 years. [index Period] Uma forma de conseguir o resultado que esperamos: os dias, meses e anos entre duas datas, é utilizando o modelo %%Period%%. Essa classe da API também possui o método %%between%%, que recebe duas instâncias de %%LocalDate%%: One way to achieve the expected result, the days, months and years between two dates, is to use the %%Period%% model. This class API also has the %%between%% method, which receives two instances of %%LocalDate%%: [code java] LocalDate now = LocalDate.now(); LocalDate anotherDate = LocalDate.of(1989, Month.JANUARY, 25); Period period = Period.between(anotherDate, now); System.out.printf("%s days, %s months and %s years", period.getDays(), period.getMonths(), period.getYears()); [/code] Note que agora o resultado será: 16 days, 0 months and 25 years. Note that now the result will be: 16 days, 0 months and 25 years. A classe %%Period%% tem uma série de métodos que auxiliam nas diversas situações que enfrentamos ao trabalhar com datas. Por exemplo, ao calcular uma diferença entre datas, é comum a necessidade de lidarmos com valores negativos. Observe o que acontece se alterarmos o ano da %%anotherDate%% para 2015: The %%Period%% class has a number of methods that help in different situations that we face when working with dates. For example, when calculating a difference between dates, we frequently deal with negative values. Notice what happens if we change the %%anotherDate%% year to 2015: [code java] LocalDate now = LocalDate.now(); LocalDate anotherDate = LocalDate.of(2015, Month.JANUARY, 25); Period period = Period.between(anotherDate, now); System.out.printf("%s days, %s months and %s years", period.getDays(), period.getMonths(), period.getYears()); [/code] A saída será algo como: -15 days, -11 months e 0 years. The output will look like: -15 days , -11 months and 0 years. Essa pode ser a saída esperada, mas caso não seja, podemos facilmente perguntar ao %%Period%% se ele é um período de valores negativos invocando o método %%isNegative%%. Caso seja, poderíamos negar seus valores com o método %%negated%%, repare: This could be the expected output, but if it's not, we can easily ask the %%Period%% if it's a period of negative values by ​​invoking the %%isNegative%% method. If we could then negate their values with the %%negated%% method, note: [code java] Period period = Period.between(anotherDate, now); if (period.isNegative()) { period = period.negated(); } System.out.printf("%s days, %s months and %s years", period.getDays(), period.getMonths(), period.getYears()); [/code] Agora a saída terá seus valores invertidos: 15 days, 11 months and 0 years. Now the output will have its values inverted​​: 15 days, 11 months and 0 years. Existem diversas outras formas de se criar um %%Period%%, além do método %%between%%. Uma delas é utilizando o método %%of(years, months, days)%% de forma fluente: There are several other ways to create a %%Period%% beyond the %%between%% method. One is using the %%of(years, months, days)%% method fluently: [code java] Period period = Period.of(2, 10, 5); [/code] Também podemos criar um período com apenas dias, meses ou anos utilizando os métodos: %%ofDays%%, %%ofMonths%% ou %%ofYears%%. We can also create a period with only days, months or years using the %%ofDays%%, %%ofMonths%% or %%ofYears%% methods. [index Duration] Mas como criar um período de horas, minutos ou segundos? A resposta é: não criamos. Neste caso, estamos interessados em outra medida de tempo, que é a %%Duration%%. Enquanto um %%Period%% considera as medidas de data (dias, meses e anos), a %%Duration%% considera as medidas de tempo (horas, minutos, segundos etc.). Sua API é muito parecida, observe: But how can we create period of hours, minutes, or seconds? The answer is: we don't. In this case, we are interested in another time measure, the %%Duration%%. While a %%Period%% considers date measures (days, months and years), the %%Duration%% considers the time measures (hours, minutes, seconds, etc.). Its API is very similar: [code java] LocalDateTime now = LocalDateTime.now(); LocalDateTime nowPlusOneHour = LocalDateTime.now().plusHours(1); Duration duration = Duration.between(now, nowPlusOneHour); if (duration.isNegative()) { duration = duration.negated(); } System.out.printf("%s hours, %s minutes and %s seconds", duration.toHours(), duration.toMinutes(), duration.getSeconds()); [/code] Repare que, como agora estamos trabalhando com tempo, utilizamos a classe %%LocalDateTime%%. Note that, since we are now working with time, we use the %%LocalDateTime%% class. [section Diferenças para o Joda Time] section Differences from Joda Time É importante lembrar que a nova API de datas (JSR-310) é baseada no Joda-Time, mas que não é uma cópia. Existem algumas diferenças de design que foram cuidadosamente apontadas pelo Stephen Colebourne em seu blog, no artigo **Why JSR-310 isn't Joda-Time**: It's good to remember that the new date API (JSR-310) is based on Joda-Time, but it's not its clone. There are some differences in the design that were carefully pointed out by Stephen Colebourne in his blog post entitled **Why JSR-310 is not Joda-Time**: [index especificação] index specification http://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html. As principais mudanças foram inspiradas pelas falhas de design do Joda-Time, como é o caso das referências nulas. No Joda-Time não só era possível fornecer valores nulos para grande maioria de seus métodos, como isso tinha um significado pra cada modelo. Por exemplo, para as representações de data e tempo o valor %%null%% significava %%1970-01-01T00:00Z%%. Para as classes %%Duration%% e %%Period%%, %%null%% significava zero. The main changes were inspired by the Java-Time design flaws, such as the null references. In Joda-Time, not only is it possible to provide null values ​​for most of its methods, but it also has a meaning for each model. For example, for the representation of date and time, the %%null%% value means %%1970-01-01T00:00Z%%. For the %%Duration%% and %%Period%% classes, %%null%% means zero. Essa é uma estratégia de design um tanto inesperada, e que pode facilmente causar bugs em nossas aplicações. This is a somewhat unexpected design strategy, and that can easily lead to bugs in our applications. Há ainda o caso da classe %%DateTime%% que implementa %%ReadableInstant%%, sendo que como uma interpretação humana de tempo não deveria. No fim, ela acaba sendo uma projeção de uma linha do tempo de máquina em uma linha do tempo humana, ficando limitada à precisão de milissegundos. There still is the case of the %%DateTime%% class which implements %%ReadableInstant%%, a human interpretation of time that shouldn't be there. It ends up being a machine time line projection in a human time line, limited to millisecond precision.