datetime.afc
Arquivo real de apostila Java em formato Tubaina, o markup que usávamos na Caelum e depois na Alura antes de migrar para Markdown. Capítulo sobre a API de datas do Java 8, circa 2014. Ver fonte bruta.
Chega de Calendar! Nova API de datas
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.
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!
A java.time vem do 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:
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.
Trabalhando com datas de forma fluente
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:
Calendar nextMonth = Calendar.getInstance();
nextMonth.add(Calendar.MONTH, 1); 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:
LocalDate nextMonth = LocalDate.now().plusMonths(1); 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:
LocalDate lastYear = LocalDate.now().minusYears(1); 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:
LocalDateTime now = LocalDateTime.now();
System.out.println(now); 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:
LocalTime now = LocalTime.now(); 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:
LocalDateTime todayAtMidday = LocalDate.now().atTime(12,0); 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:
LocalTime now = LocalTime.now();
LocalDate today = LocalDate.now();
LocalDateTime dateAndTime = today.atTime(now); 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.
ZonedDateTime dateTimeAndTimezone =
dateAndTime.atZone(ZoneId.of("America/Los_Angeles")); 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:
LocalDateTime withoutTimeZone = dateTimeAndTimezone.toLocalDateTime(); 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:
LocalDate date = LocalDate.of(2014, 12, 25);
LocalDateTime dateTime = LocalDateTime.of(2014, 12, 25, 10, 30); 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.
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 setters, os novos modelos imutáveis possuem os métodos withs 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 setters, the new immutable models have the withs methods to facilitate the insertion of information. To modify a year of a LocalDate, for example, we could use the withYear method. Like this:
LocalDate dataDoPassado = LocalDate.now().withYear(1988); Mas e para recuperar essas informações? Podemos utilizar seus métodos gets, 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 gets methods according to the value we're looking for. For example, getYear to know the year, or getMonth for the month, and so on.
LocalDate longTimeAgo = LocalDate.now().withYear(1988);
System.out.println(longTimeAgo.getYear()); 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:
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)); 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:
@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;
} 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:
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)); 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:
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)); 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:
System.out.println("The current day of month is: "+ MonthDay.now().getDayOfMonth()); Você pode pegar o YearMonth de uma determinada data.
You can take the YearMonth of a specific date.
YearMonth ym = YearMonth.from(date);
System.out.println(ym.getMonth() + " " + ym.getYear()); 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.
Enums no lugar de constantes
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:
System.out.println(LocalDate.of(2014, 12, 25));
System.out.println(LocalDate.of(2014, Month.DECEMBER, 25)); 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:
System.out.println(Month.DECEMBER.firstMonthOfQuarter());
System.out.println(Month.DECEMBER.plus(2));
System.out.println(Month.DECEMBER.minus(1)); O resultado desse código será:
This code's result will be:
OCTOBER
FEBRUARY
NOVEMBER 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:
Locale us = Locale.US;
System.out.println(Month.DECEMBER
.getDisplayName(TextStyle.FULL, us));
System.out.println(Month.DECEMBER
.getDisplayName(TextStyle.SHORT, us));
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:
December
Dec 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!
Formatando com a nova API de datas
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:
LocalDateTime now = LocalDateTime.now();
String result = now.format(DateTimeFormatter.ISO_LOCAL_TIME); 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:
LocalDateTime now = LocalDateTime.now();
now.format(DateTimeFormatter.ofPattern("MM-dd-yyyy")); 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:
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("MM-dd-yyyy");
String result = now.format(formatter);
LocalDate parsed = LocalDate.parse(result, formatter); 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!
Datas inválidas
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:
Calendar instante = Calendar.getInstance();
instante.set(2014, Calendar.FEBRUARY, 30);
SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
System.out.println(dateFormat.format(instante.getTime())); 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:
LocalDate.of(2014, Month.FEBRUARY, 30); 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:
LocalDateTime invalidHour = LocalDate.now().atTime(25, 0); 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.
Duração e Período
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 Calendars? 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 Calendars? You probably did this:
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; 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.
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:
LocalDate now = LocalDate.now();
LocalDate anotherDate = LocalDate.of(1989, Month.JANUARY, 25);
long days = ChronoUnit.DAYS.between(anotherDate, now); 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:
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); 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.
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:
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()); 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:
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()); 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:
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()); 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:
Period period = Period.of(2, 10, 5); 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.
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:
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()); 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.
Diferenças para o 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**:
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.