Duration and Period in Java
Duration in Java is the class java.time.Duration, which stores an exact amount of time in seconds and nanoseconds, for example PT1H30M (1 hour 30 minutes) or PT12.87656S. Its counterpart is Period (java.time.Period): an amount of time in calendar years, months and days, for example P5Y2M9D. Both classes were added in Java 8, and both are immutable and thread-safe.
Add Period.ofDays(1) and Duration.ofDays(1) to noon on Saturday, 30 March 2024 in Berlin: the first gives Sunday 12:00, the second gives 13:00. The clocks went forward that night, the calendar day lasted only 23 hours, and only Period took that into account. Choosing between the two classes is not a matter of taste.
Java Duration vs Period: what is the difference
Duration measures physical time: a day in it is exactly 86,400 seconds. Period measures calendar time: a month can last 28–31 days, and a day can last 23 or 25 hours when the clocks change. Use Duration for timeouts and how long an operation took, and Period for age, contract terms and «in 2 months».
| Feature | Duration | Period |
|---|---|---|
| What it stores | Seconds (long) and nanoseconds (int) | Years, months, days (three int values) |
toString() example | PT1H30M, PT48H (days are never shown) | P1Y2M3D, P14D |
| Difference between two values | Duration.between() — LocalTime, LocalDateTime, Instant | Period.between() — LocalDate only |
| Can be added to | LocalTime, LocalDateTime, ZonedDateTime, Instant | LocalDate, LocalDateTime, ZonedDateTime |
| Daylight saving time | Ignored: a day is always exactly 24 hours | Respected: keeps the same wall-clock time |
equals() | ofHours(1).equals(ofMinutes(60)) — true | ofMonths(12).equals(ofYears(1)) — false |
| When to use it | Timeouts, execution time, call length | Age, subscription term, warranty period |
Here is the example from the introduction in full. On 31 March 2024 the clocks in Germany jumped from 02:00 to 03:00:
import java.time.Duration;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class DurationVsPeriodExample {
public static void main(String[] args) {
ZonedDateTime base = ZonedDateTime.of(2024, 3, 30, 12, 0, 0, 0, ZoneId.of("Europe/Berlin"));
System.out.println(base);
System.out.println(base.plus(Period.ofDays(1)));
System.out.println(base.plus(Duration.ofDays(1)));
}
} 2024-03-30T12:00+01:00[Europe/Berlin]
2024-03-31T12:00+02:00[Europe/Berlin]
2024-03-31T13:00+02:00[Europe/Berlin] Period.ofDays(1) moved the calendar date and kept 12:00, while Duration.ofDays(1) added 24 real hours, which ended at 13:00 because of the skipped hour. Both classes implement the TemporalAmount interface, so they are passed to the same plus() method. For an overview of all classes in the package, see Java Date and Time API.
Creating a Duration with of*()
A Duration is created with static factory methods: ofDays(), ofHours(), ofMinutes(), ofSeconds(), ofMillis(), ofNanos() and the generic of(amount, ChronoUnit). The class has no public constructor. A duration can be negative, and the zero duration is available as the constant Duration.ZERO.
Creating java.time.Duration objects:
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class DurationExample1 {
public static void main(String[] args) {
Duration oneDay = Duration.ofDays(1);
Duration oneHour = Duration.ofHours(1);
Duration oneMin = Duration.ofMinutes(1);
Duration tenSeconds = Duration.ofSeconds(10);
Duration twoSeconds = Duration.ofSeconds(1, 1_000_000_000);
Duration oneSecondFromMillis = Duration.ofMillis(1000);
Duration oneSecondFromNanos = Duration.ofNanos(1_000_000_000);
Duration oneSecond = Duration.of(1, ChronoUnit.SECONDS);
Duration ninetySeconds = Duration.ofSeconds(90);
Duration oneAndHalfSeconds = Duration.ofMillis(1500);
Duration minusNinety = Duration.ofSeconds(-90);
System.out.println("oneDay: " + oneDay);
System.out.println("oneHour: " + oneHour);
System.out.println("oneMin: " + oneMin);
System.out.println("tenSeconds: " + tenSeconds);
System.out.println("twoSeconds: " + twoSeconds);
System.out.println("oneSecondFromMillis: " + oneSecondFromMillis);
System.out.println("oneSecondFromNanos: " + oneSecondFromNanos);
System.out.println("oneSecond: " + oneSecond);
System.out.println("ninetySeconds: " + ninetySeconds);
System.out.println("oneAndHalfSeconds: " + oneAndHalfSeconds);
System.out.println("minusNinety: " + minusNinety);
}
} oneDay: PT24H
oneHour: PT1H
oneMin: PT1M
tenSeconds: PT10S
twoSeconds: PT2S
oneSecondFromMillis: PT1S
oneSecondFromNanos: PT1S
oneSecond: PT1S
ninetySeconds: PT1M30S
oneAndHalfSeconds: PT1.5S
minusNinety: PT-1M-30S Look at the first line: Duration.ofDays(1) prints as PT24H. Internally only seconds are stored, and toString() never shows days. In the two-argument form ofSeconds(1, 1_000_000_000) the extra billion nanoseconds turns into a whole second, so the result is PT2S.
The of() method accepts only units with an exact length, from NANOS to DAYS. A month or a year of variable length does not fit into a Duration:
Duration.of(1, ChronoUnit.DAYS); // PT24H
Duration.of(1, ChronoUnit.MONTHS); // UnsupportedTemporalTypeException: Unit must not have an estimated duration Duration.between: the time between two moments
Duration.between(start, end) returns the exact time between two values that have a time of day: LocalTime, LocalDateTime, ZonedDateTime or Instant. If end is earlier than start, the duration is negative. The method does not work with LocalDate, because a date has no seconds.
Calculating the length of a working day:
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalTime;
public class DurationBetweenExample {
public static void main(String[] args) {
LocalTime start = LocalTime.of(9, 15);
LocalTime end = LocalTime.of(17, 45, 30);
Duration work = Duration.between(start, end);
System.out.println(work); // PT8H30M30S
System.out.println(work.toMinutes()); // 510
System.out.println(Duration.between(end, start)); // PT-8H-30M-30S
LocalDate d1 = LocalDate.of(2024, 1, 1);
LocalDate d2 = LocalDate.of(2024, 1, 2);
System.out.println(Duration.between(d1.atStartOfDay(), d2.atStartOfDay())); // PT24H
Duration.between(d1, d2); // UnsupportedTemporalTypeException: Unsupported unit: Seconds
}
} If you need a single number in a specific unit rather than an object, use ChronoUnit.MINUTES.between(start, end) — it also returns 510. The difference between two Instant values is covered in Instant in Java, and between two LocalDateTime values in LocalDateTime in Java. To measure how long a piece of code runs, use System.nanoTime() — see LocalTime.now() and System.nanoTime().
Duration to seconds and minutes, hh:mm:ss format
The to*() methods convert the whole duration into one unit: toHours(), toMinutes(), toMillis(), toNanos(), plus getSeconds(). The to*Part() methods (Java 9+) return a single component: toHoursPart(), toMinutesPart(), toSecondsPart(). Any fractional part is truncated during conversion.
Example for a duration of 2 hours 30 minutes 30.5 seconds:
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class DurationGetExample {
public static void main(String[] args) {
Duration d = Duration.ofSeconds(9030, 500_000_000);
System.out.println(d); // PT2H30M30.5S
System.out.println(d.getSeconds()); // 9030
System.out.println(d.getNano()); // 500000000
System.out.println(d.toDays()); // 0
System.out.println(d.toHours()); // 2
System.out.println(d.toMinutes()); // 150
System.out.println(d.toMillis()); // 9030500
System.out.println(d.get(ChronoUnit.SECONDS)); // 9030
// Java 9+
System.out.println(d.toSeconds()); // 9030
System.out.println(d.toHoursPart()); // 2
System.out.println(d.toMinutesPart()); // 30
System.out.println(d.toSecondsPart()); // 30
System.out.println(d.toMillisPart()); // 500
}
} getSeconds() returns all 9030 seconds, not the «seconds on the clock face». To convert a Java duration to seconds, use getSeconds() or, since Java 9, toSeconds(). The generic get() supports only ChronoUnit.SECONDS and ChronoUnit.NANOS: d.get(ChronoUnit.MINUTES) throws UnsupportedTemporalTypeException: Unsupported unit: Minutes.
There is no built-in formatter for Duration: DateTimeFormatter works with dates and times of day, not with amounts of time. A string like «02:30:30» is assembled from the parts:
Duration d = Duration.ofSeconds(9030);
Duration big = Duration.ofSeconds(100_000);
// Java 9+
String hms = String.format("%02d:%02d:%02d", d.toHours(), d.toMinutesPart(), d.toSecondsPart());
// Java 8
String hms8 = String.format("%02d:%02d:%02d", d.toHours(), d.toMinutes() % 60, d.getSeconds() % 60);
String bigHms = String.format("%02d:%02d:%02d", big.toHours(), big.toMinutesPart(), big.toSecondsPart());
System.out.println(hms); // 02:30:30
System.out.println(hms8); // 02:30:30
System.out.println(big); // PT27H46M40S
System.out.println(bigHms); // 27:46:40 You will sometimes see the trick LocalTime.MIDNIGHT.plus(d).format(...). It only works for durations shorter than a day: for big the time wraps past midnight and shows 03:46:40.
Changing and comparing a Duration
A duration is changed with the plus*() and minus*() methods (days, hours, minutes, seconds, milliseconds, nanoseconds or another Duration), as well as multipliedBy(), dividedBy(), negated() and abs(). The withSeconds() and withNanos() methods replace the value outright. Every call returns a new object, so the result must be assigned.
Arithmetic on a one-hour duration:
import java.time.Duration;
public class DurationPlusExample {
public static void main(String[] args) {
Duration base = Duration.ofHours(1);
System.out.println(base.plusMinutes(30)); // PT1H30M
System.out.println(base.plusSeconds(15)); // PT1H15S
System.out.println(base.plusMillis(500)); // PT1H0.5S
System.out.println(base.plusDays(1)); // PT25H
System.out.println(base.plus(Duration.ofMinutes(45))); // PT1H45M
System.out.println(base.minusMinutes(90)); // PT-30M
System.out.println(base.multipliedBy(3)); // PT3H
System.out.println(base.dividedBy(4)); // PT15M
System.out.println(base.negated()); // PT-1H
System.out.println(base.minusMinutes(90).abs()); // PT30M
// How many 15-minute slots fit into 2 hours (Java 9+)
System.out.println(Duration.ofHours(2).dividedBy(Duration.ofMinutes(15))); // 8
System.out.println(Duration.ofMinutes(5).withSeconds(10)); // PT10S
System.out.println(Duration.ofMinutes(5).withNanos(500_000_000)); // PT5M0.5S
}
} The surprising one is withSeconds(10): it does not change the «seconds» inside the 5 minutes, it replaces the entire duration with 10 seconds. Zero minutes are skipped by toString(), which is why 1 hour 15 seconds prints as PT1H15S.
To compare durations, use equals(), compareTo() (the class implements Comparable<Duration>), isZero(), isNegative() and, since Java 18, isPositive():
import java.time.Duration;
public class DurationCompareExample {
public static void main(String[] args) {
Duration a = Duration.ofMinutes(90);
Duration b = Duration.ofHours(1).plusMinutes(30);
Duration c = Duration.ofHours(2);
System.out.println(a.equals(b)); // true
System.out.println(a.compareTo(c) < 0); // true
System.out.println(a.minus(c).isNegative()); // true
System.out.println(a.minus(b).isZero()); // true
System.out.println(Duration.ofHours(1).equals(Duration.ofMinutes(60))); // true
}
} Two durations are equal when their total seconds and nanoseconds are equal, no matter which method created them. Compare the result of compareTo() with zero, not with -1.
Creating a Period with of*()
java.time.Period stores an amount of time in years, months and days, for example 5 years, 2 months and 9 days (P5Y2M9D). It is created with of(years, months, days), ofYears(), ofMonths(), ofWeeks() and ofDays(); the zero period is Period.ZERO. Values are not normalised: 14 months stay 14 months.
Creating Period objects:
import java.time.Period;
public class PeriodExample1 {
public static void main(String[] args) {
Period period5y4m3d = Period.of(5, 4, 3);
Period period2d = Period.ofDays(2);
Period period2m = Period.ofMonths(2);
Period period14d = Period.ofWeeks(2);
Period period2y = Period.ofYears(2);
Period period14m40d = Period.of(0, 14, 40);
System.out.println(period5y4m3d); // P5Y4M3D
System.out.println(period2d); // P2D
System.out.println(period2m); // P2M
System.out.println(period14d); // P14D
System.out.println(period2y); // P2Y
System.out.println(period14m40d); // P14M40D
System.out.println(Period.ZERO); // P0D
}
} A Period has no weeks field: ofWeeks(2) immediately becomes 14 days. Days, in turn, are never converted into months, because the length of a month is not known in advance.
Period.between and ChronoUnit.between
Period.between(start, end) returns the difference between two LocalDate values as «N years, M months, K days»: whole months are counted first, then the remaining days. The end date is exclusive. If you need a single number — total days or total months — use ChronoUnit.DAYS.between() or ChronoUnit.MONTHS.between().
The difference between two dates and an age calculation:
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class PeriodExample2 {
public static void main(String[] args) {
LocalDate march2003 = LocalDate.of(2003, 3, 1);
LocalDate may2003 = LocalDate.of(2003, 5, 1);
System.out.println(Period.between(march2003, may2003)); // P2M
System.out.println(Period.between(may2003, march2003)); // P-2M
LocalDate birthday = LocalDate.of(1990, 8, 25);
LocalDate today = LocalDate.of(2024, 3, 15);
Period age = Period.between(birthday, today);
System.out.println(age); // P33Y6M19D
System.out.println("Full years: " + age.getYears()); // Full years: 33
System.out.println(ChronoUnit.YEARS.between(birthday, today)); // 33
}
} What does this code print? From 31 January to 1 March 2024 there are 30 days, but Period sees it differently:
LocalDate start = LocalDate.of(2024, 1, 31);
LocalDate end = LocalDate.of(2024, 3, 1);
Period p = Period.between(start, end);
System.out.println(p); // P1M1D
System.out.println(p.getDays()); // 1
System.out.println(ChronoUnit.DAYS.between(start, end)); // 30
System.out.println(ChronoUnit.MONTHS.between(start, end)); // 1
System.out.println(start.until(end)); // P1M1D One month after 31 January is 29 February (the last day of the shorter month), and 1 day remains until 1 March. That is why getDays() returns only the leftover days, not the total. For «how many days between two dates» you need ChronoUnit.DAYS.between() — more on that in LocalDate: days between two dates. Period.between() accepts only LocalDate; for a LocalDateTime, call toLocalDate() first.
Period methods: get, with, plus, minus, normalized
The parts of a period are read with getYears(), getMonths(), getDays() and get(ChronoUnit), replaced with withYears(), withMonths(), withDays(), and shifted with plus*() and minus*(). The normalized() method rolls excess months into years, and toTotalMonths() returns the total number of months.
Using the get methods of Period:
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class PeriodExample3 {
public static void main(String[] args) {
Period period5y4m3d = Period.of(5, 4, 3);
int days = period5y4m3d.getDays();
int months = period5y4m3d.getMonths();
int years = period5y4m3d.getYears();
long days2 = period5y4m3d.get(ChronoUnit.DAYS);
System.out.println("Days: " + days);
System.out.println("Months: " + months);
System.out.println("Years: " + years);
System.out.println("Days: " + days2);
System.out.println("Total months: " + period5y4m3d.toTotalMonths());
System.out.println("Units: " + period5y4m3d.getUnits());
}
} Days: 3
Months: 4
Years: 5
Days: 3
Total months: 64
Units: [Years, Months, Days] get() understands only YEARS, MONTHS and DAYS: period.get(ChronoUnit.WEEKS) throws UnsupportedTemporalTypeException: Unsupported unit: Weeks.
To replace the years, months or days, use the with methods of Period:
import java.time.Period;
public class PeriodExample4 {
public static void main(String[] args) {
Period period2d = Period.ofDays(2);
Period period8d = period2d.withDays(8);
Period period2y1m2d = period2d.withYears(2).withMonths(1);
System.out.println(period2d); // P2D
System.out.println(period8d); // P8D
System.out.println(period2y1m2d); // P2Y1M2D
}
} To add or subtract years, months or days, use the plus and minus methods of Period:
import java.time.Period;
public class PeriodExample5 {
public static void main(String[] args) {
Period period5y4m3d = Period.of(5, 4, 3);
Period period2y = Period.ofYears(2);
// Adding
System.out.println(period5y4m3d.plusYears(4)); // P9Y4M3D
System.out.println(period5y4m3d.plusMonths(3)); // P5Y7M3D
System.out.println(period5y4m3d.plusDays(3)); // P5Y4M6D
System.out.println(period5y4m3d.plus(period2y)); // P7Y4M3D
// Subtracting
System.out.println(period5y4m3d.minusYears(2)); // P3Y4M3D
System.out.println(period5y4m3d.minusMonths(1)); // P5Y3M3D
System.out.println(period5y4m3d.minusDays(1)); // P5Y4M2D
System.out.println(period5y4m3d.minus(period2y)); // P3Y4M3D
// Multiplying and changing the sign
System.out.println(period5y4m3d.multipliedBy(2)); // P10Y8M6D
System.out.println(period5y4m3d.negated()); // P-5Y-4M-3D
}
} Only a «calendar» amount can be passed to plus(): Period.ofDays(1).plus(Duration.ofHours(2)) throws DateTimeException: Unit must be Years, Months or Days, but was Seconds.
The normalized() method turns months above 12 into years but leaves the days alone:
System.out.println(Period.of(1, 14, 40).normalized()); // P2Y2M40D
System.out.println(Period.of(1, -1, 0).normalized()); // P11M
System.out.println(Period.ofMonths(12).equals(Period.ofYears(1))); // false
System.out.println(Period.ofMonths(12).normalized().equals(Period.ofYears(1))); // true Unlike Duration, Period.equals() compares the fields one by one: 12 months and 1 year are different periods until you call normalized(). And Period has no compareTo() at all — without a reference date you cannot tell which is longer, 1 month or 30 days.
parse() and toString(): ISO-8601 PT... and P...
Both classes print and parse the ISO-8601 format. A Duration is written as PT followed by hours H, minutes M and seconds S (for example, PT1H30M); a Period as P followed by years Y, months M, weeks W and days D (for example, P1Y2M3D). The letter T separates time from dates: P1M is a month, PT1M is a minute.
Parsing strings with Duration.parse() and Period.parse():
import java.time.Duration;
import java.time.Period;
public class ParseExample {
public static void main(String[] args) {
System.out.println(Duration.parse("PT1H30M")); // PT1H30M
System.out.println(Duration.parse("PT90M")); // PT1H30M
System.out.println(Duration.parse("P2DT3H")); // PT51H
System.out.println(Duration.parse("PT-1.5S")); // PT-1.5S
System.out.println(Duration.parse("-PT6H3M")); // PT-6H-3M
System.out.println(Period.parse("P1Y2M3D")); // P1Y2M3D
System.out.println(Period.parse("P2W")); // P14D
System.out.println(Period.parse("P1Y2W3D")); // P1Y17D
System.out.println(Period.parse("-P1M")); // P-1M
}
} Duration.parse() accepts days (P2DT3H) but immediately converts them into 24-hour blocks, so toString() shows PT51H. Years and months are not allowed in a Duration, and time is not allowed in a Period. Strings in any other shape are rejected:
Duration.parse("1:30"); // DateTimeParseException: Text cannot be parsed to a Duration
Duration.parse("P1M"); // DateTimeParseException: Text cannot be parsed to a Duration
Period.parse("PT1H"); // DateTimeParseException: Text cannot be parsed to a Period Turning «01:30» into a Duration
Duration.parse() does not understand the «hours:minutes» format. If the value comes from a form or a CSV file, split it yourself: String[] p = "01:30".split(":"); Duration d = Duration.ofHours(Long.parseLong(p[0])).plusMinutes(Long.parseLong(p[1])); — the result is PT1H30M. To turn it back into «01:30», use String.format() as shown in hh:mm:ss format.
Common mistakes with Duration and Period
Most bugs with Duration and Period come from mixing up the two classes or from treating one component of a value as the whole value. Both classes are immutable, so the result of plus*() and with*() must also be assigned. Typical cases and their fixes:
| Mistake | What happens | How to fix it |
|---|---|---|
Period.between(a, b).getDays() as «total days» | Returns only the leftover days: 1 for P1M1D, not 30 | ChronoUnit.DAYS.between(a, b) |
duration.getSeconds() as the seconds for «hh:mm:ss» | Returns 9030 for PT2H30M30S, not 30 | toSecondsPart() (Java 9+) or getSeconds() % 60 |
Duration.between(localDate1, localDate2) | UnsupportedTemporalTypeException: Unsupported unit: Seconds | Period.between() or atStartOfDay() on both dates |
localDate.plus(Duration.ofDays(1)) | UnsupportedTemporalTypeException: Unsupported unit: Seconds | localDate.plus(Period.ofDays(1)) or plusDays(1) |
localTime.plus(Period.ofDays(1)) | UnsupportedTemporalTypeException: Unsupported unit: Days | Use only Duration with a time of day |
Duration.of(1, ChronoUnit.MONTHS) | UnsupportedTemporalTypeException: Unit must not have an estimated duration | Period.ofMonths(1) |
Duration.ofDays(1) for «tomorrow at the same time» in a zone with DST | Twice a year the result is off by one hour | zonedDateTime.plus(Period.ofDays(1)) |
Period.ofMonths(12).equals(Period.ofYears(1)) | false: the fields are compared one by one | Compare after normalized() or via toTotalMonths() |
Frequently asked questions
How do I store a Duration in a database or in JSON?
JDBC has no standard SQL type for Duration, so the usual choice is a number (duration.toMillis() or getSeconds() in a BIGINT column) or the ISO-8601 string from toString(), such as PT1H30M, restored with Duration.ofMillis() or Duration.parse(). Hibernate maps a Duration field on its own. Jackson with JavaTimeModule writes a Duration as a number of seconds by default, and as the string PT1H30M when WRITE_DURATIONS_AS_TIMESTAMPS is disabled.
Which standard Java APIs accept a Duration for timeouts?
HttpClient.Builder.connectTimeout(Duration) and HttpRequest.Builder.timeout(Duration) since Java 11, Thread.sleep(Duration) and Thread.join(Duration) since Java 19. If an API expects a long plus a TimeUnit, pass duration.toMillis() and TimeUnit.MILLISECONDS. The reverse is Duration.of(n, timeUnit.toChronoUnit()) — toChronoUnit() exists since Java 9.
Is there an interval class with a start and an end in java.time?
No. Duration and Period describe only the length of a span and do not remember where it started. Store an interval as a pair of values, for example two Instant or two LocalDate objects, and get its length with between(). Ready-made Interval and LocalDateRange classes are available in the ThreeTen-Extra library by the author of java.time.
How do I represent «1 day and 2 hours» as a single value?
The JDK has no class that holds calendar days and hours at the same time. Duration.ofDays(1).plusHours(2) gives PT26H, treating the day as exactly 24 hours. If the calendar day matters, add the parts one by one: dateTime.plus(Period.ofDays(1)).plus(Duration.ofHours(2)). Why these are not the same is explained in Java Duration vs Period.
Comments