Period.between()
Get the calendar gap between two dates as years, months and days, the right tool for ages, tenures and subscription terms.
public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$startDateInclusive | LocalDate | The start date, included. |
$endDateExclusive | LocalDate | The end date, excluded. |
Return value
PeriodThe period between the dates in years, months and days; negative if the end is before the start.
Errors
NullPointerException if either date is null. It does not throw for valid dates.
Period.between(start, end) answers "how many years, months and days apart are these two dates?" the way a person would count on a calendar. The result is a Period such as P35Y11M11D. It takes LocalDate values only; for times, use Duration.
How it counts
It counts whole months first, then the remaining days. From 2 November 1990 to 13 October 2026 is 35 years, 11 months and 11 days. Because months have different lengths, the result is not a fixed number of days, and that is the point: an age, a tenure or a contract term is defined in calendar units.
The getDays() trap
getYears(), getMonths() and getDays() return the three components separately. They are not totals. Between 1 January and 15 March, getDays() is 14, not 73. This is the most common bug with Period. For total days use ChronoUnit.DAYS.between(start, end); for total months use toTotalMonths() on the period.
Ages and age checks
Period.between(birthDate, today).getYears() is the correct age calculation, including leap-day birthdays: someone born on 29 February 2008 turns 18 on 1 March 2026, since 2026 has no 29 February. Get today from LocalDate.now(clock) in the user's zone, otherwise the age changes at midnight UTC rather than at the user's midnight.
Other things to know
- If the end is before the start, every component is negative or zero, and
isNegative()returns true. Period.ofMonths(14)stays as 14 months until you callnormalized(), which givesP1Y2M. Normalising never changes days.- A
Periodadded to a date withdate.plus(period)adds the years and months together as one total of months, then the days, clamping to the month end where needed.
In APIs, an ISO period string such as P1Y or P30D is a compact way to send a subscription term or a retention setting, and Period.parse reads it back.
Example
When to use it
- Calculating a user's age from their date of birth to enforce an 18+ rule at sign-up.
- Showing how long a customer has been subscribed, in years and months, on an account page.
- Storing a plan's billing term as an ISO period such as P1M and applying it to the start date.
Frequently asked questions
How do I calculate age from a date of birth in Java?
Use Period.between(birthDate, today).getYears(), with today taken from LocalDate.now(clock) in the user's zone.
Why does Period.getDays() return the wrong number of days?
getDays() is only the days part after whole years and months are removed. For the total, use ChronoUnit.DAYS.between(start, end).
What is the difference between Period and Duration?
Period counts calendar years, months and days between LocalDate values. Duration counts exact seconds and nanoseconds between values with a time part.
More examples
An 18+ check with a leap-day birthday
2026 has no 29 February, so a person born on that date reaches 18 full years on 1 March. Period handles this without special cases.
import java.time.*;
boolean isAdult(LocalDate birth, LocalDate today) {
return Period.between(birth, today).getYears() >= 18;
}
void main() {
LocalDate born = LocalDate.of(2008, 2, 29);
for (LocalDate day : List.of(LocalDate.of(2026, 2, 28), LocalDate.of(2026, 3, 1))) {
IO.println(day + " " + Period.between(born, day) + " adult=" + isAdult(born, day));
}
}
// Output:
// 2026-02-28 P17Y11M30D adult=false
// 2026-03-01 P18Y1D adult=trueNormalising and applying a subscription term
normalized() folds 12 months into a year, and toTotalMonths() gives a single comparable number. Period.parse reads the same ISO-8601 form that toString writes.
import java.time.*;
void main() {
Period term = Period.ofMonths(14);
IO.println(term + " -> " + term.normalized());
IO.println("total months: " + term.toTotalMonths());
LocalDate start = LocalDate.of(2026, 1, 31);
IO.println("renews on " + start.plus(term));
IO.println("parsed: " + Period.parse("P1Y2M").equals(term.normalized()));
}
// Output:
// P14M -> P1Y2M
// total months: 14
// renews on 2027-03-31
// parsed: trueNegative periods for overdue items
When the end date is earlier, all components come back negative and isNegative() is true. negated() flips the sign for display.
import java.time.*;
String describe(LocalDate due, LocalDate today) {
Period p = Period.between(today, due);
if (p.isNegative()) {
return "overdue by " + p.negated();
}
return "due in " + p;
}
void main() {
LocalDate today = LocalDate.of(2026, 3, 20);
IO.println(describe(LocalDate.of(2026, 5, 1), today));
IO.println(describe(LocalDate.of(2026, 2, 15), today));
}
// Output:
// due in P1M11D
// overdue by P1M5D
Discussion