Temporal Arithmetic with Periods
The Period class provides plus and minus methods that return a copy of the original object that has been incremented or decremented by a specific amount specified in terms of a date unit—for example, as a number of years, months, or days. As the following code snippets show, only the value of a specific date unit is changed; the values of other date fields are unaffected. There is no implicit normalization performed, unless the normalized() method that normalizes only the months is called, adjusting the values of the months and years as necessary.
Period p6 = Period.of(2, 10, 30) // P2Y10M30D
.plusDays(10) // P2Y10M40D
.plusMonths(8) // P2Y18M40D
.plusYears(1) // P3Y18M40D
.normalized(); // P4Y6M40D
We can do simple arithmetic with periods. The code examples below use the plus() and the minus() methods of the Period class that take a TemporalAmount as an argument. Both the Period and the Duration classes implement the TemporalAmount interface. In the last assignment statement, we have shown the state of both new Period objects that are created.
Period p7 = Period.of(1, 1, 1); // P1Y1M1D
Period p8 = Period.of(2, 12, 30); // P2Y12M30D
Period p9 = p8.minus(p7); // P1Y11M29D
p8 = p8.plus(p7).plus(p8); // P3Y13M31D, P5Y25M61D
Period plusYears/minusYears(long years)
Period plusMonths/minusMonths(long months)
Period plusDays/minusDays(long days)
Return a copy of this period, with the specified value for the date unit added or subtracted. The values of other date units are unaffected.
Period plus(TemporalAmount amount)
Period minus(TemporalAmount amount)
Return a copy of this period, with the specified temporal amount added or subtracted. The amount is of the interface type TemporalAmount that is implemented by the classes Period and Duration, but only Period is valid here. The operation is performed separately on each date unit. There is no normalization performed. A DateTimeException is thrown if the operation cannot be performed.
Period normalized()
Returns a copy of this period where the years and months are normalized. The number of days is not affected.
Period negated()
Period multipliedBy(int scalar)
Return a new instance of Period where the value of each date unit in this period is individually negated or multiplied by the specified scalar, respectively.
We can also do simple arithmetic with dates and periods. The following code uses the plus() and minus() methods of the LocalDate class that take a TemporalAmount as an argument (p. 1040). Note the adjustments performed to the month and the day fields to return a valid date in the last assignment statement.
Period p10 = Period.of(1, 1, 1); // P1Y1M1D
LocalDate date1 = LocalDate.of(2021, 3, 1); // 2021-03-01
LocalDate date2 = date1.plus(p10); // 2022-04-02
LocalDate date3 = date1.minus(p10); // 2020-01-31
We can add and subtract periods from LocalDate and LocalDateTime objects, but not from LocalTime objects, as a LocalTime object has only time fields.
LocalTime time = LocalTime.NOON;
time = time.plus(p10); // java.time.temporal.UnsupportedTemporalTypeException
Example 17.5 is a simple example to illustrate implementing period-based loops. The method reserveDates() at (1) is a stub for reserving certain dates, depending on the period passed as an argument. The for(;;) loop at (2) uses the Local-Date.isBefore() method to terminate the loop, and the LocalDate.plus() method to increment the current date with the specified period.
Example 17.5 Period-Based Loop
import java.time.LocalDate;
import java.time.Period;
public class PeriodBasedLoop {
public static void main(String[] args) {
reserveDates(Period.ofDays(7),
LocalDate.of(2021, 10, 20), LocalDate.of(2021, 11, 20));
System.out.println();
reserveDates(Period.ofMonths(1),
LocalDate.of(2021, 10, 20), LocalDate.of(2022, 1, 20));
System.out.println();
reserveDates(Period.of(0, 1, 7),
LocalDate.of(2021, 10, 20), LocalDate.of(2022, 1, 21));
}
public static void reserveDates(Period period, // (1)
LocalDate fromDate,
LocalDate toDateExclusive) {
System.out.println(“Start date: ” + fromDate);
for (LocalDate date = fromDate.plus(period); // (2)
date.isBefore(toDateExclusive);
date = date.plus(period)) {
System.out.println(“Reserved (” + period + “): ” + date);
}
System.out.println(“End date: ” + toDateExclusive);
}
}
Output from the program:
Start date: 2021-10-20
Reserved (P7D): 2021-10-27
Reserved (P7D): 2021-11-03
Reserved (P7D): 2021-11-10
Reserved (P7D): 2021-11-17
End date: 2021-11-20
Start date: 2021-10-20
Reserved (P1M): 2021-11-20
Reserved (P1M): 2021-12-20
End date: 2022-01-20
Start date: 2021-10-20
Reserved (P1M7D): 2021-11-27
Reserved (P1M7D): 2022-01-03
End date: 2022-01-21
We conclude this section with Example 17.6, which brings together some of the methods of the Date and Time API. Given a date of birth, the method birthdayInfo() at (1) calculates the age and the time until the next birthday. The age is calculated at (2) using the Period.between() method, which computes the period between two dates. The date for the next birthday is set at (3) as the birth date with the current year. The if statement at (4) adjusts the next birthday date by 1 year at (5), if the birthday has already passed. The statement at (6) calculates the time until the next birthday by calling the LocalDate.until() method. We could also have used the Period.between() method at (6). The choice between these methods really depends on which method makes the code more readable in a given context.
Example 17.6 More Temporal Arithmetic
import java.time.LocalDate;
import java.time.Month;
import java.time.Period;
public class ActYourAge {
public static void main(String[] args) {
birthdayInfo(LocalDate.of(1981, Month.AUGUST, 19));
birthdayInfo(LocalDate.of(1935, Month.JANUARY, 8));
}
public static void birthdayInfo(LocalDate dateOfBirth) { // (1)
LocalDate today = LocalDate.now();
System.out.println(“Today: ” + today);
System.out.println(“Date of Birth: ” + dateOfBirth);
Period p1 = Period.between(dateOfBirth, today); // (2)
System.out.println(“Age: ” +
p1.getYears() + ” years, ” +
p1.getMonths() + ” months, and ” +
p1.getDays() + ” days”);
LocalDate nextBirthday = dateOfBirth.withYear(today.getYear()); // (3)
if (nextBirthday.isBefore(today) || // (4)
nextBirthday.isEqual(today)) {
nextBirthday = nextBirthday.plusYears(1); // (5)
}
Period p2 = today.until(nextBirthday); // (6)
System.out.println(“Birthday in ” + p2.getMonths() + ” months and ” +
p2.getDays() + ” days”);
}
}
Possible output from the program:
Today: 2021-03-05
Date of Birth: 1981-08-19
Age: 39 years, 6 months, and 14 days
Birthday in 5 months and 14 days
Today: 2021-03-05
Date of Birth: 1935-01-08
Age: 86 years, 1 months, and 25 days
Birthday in 10 months and 3 days