Period.between()

Get the calendar gap between two dates as years, months and days, the right tool for ages, tenures and subscription terms.

Signature
public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$startDateInclusiveLocalDateThe start date, included.
$endDateExclusiveLocalDateThe 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 call normalized(), which gives P1Y2M. Normalising never changes days.
  • A Period added to a date with date.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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · java
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=true

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

Example · java
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: true

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

Example · java
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

  • Be the first to comment on this lesson.