skip to content

How do you construct, combine, and normalize Duration and Period amounts, and what are the gotchas?

level: middleimportance: should knowfreq 45%

answer

  1. Immutable → must assign the result of plus/minus
  2. Duration ISO = PT1H30M (T present); Period ISO = P1Y2M10D (no T)
  3. Period.ofWeeks(2) → 14 days (no weeks field)
  4. Period.normalized() rolls months↔years only, never days
  5. Can't add a Period and a Duration directly — apply both to a temporal

basics

~20 s

Build them with factories like Duration.ofMinutes(90) or Period.of(1,2,10). Combine with plus/minus (they're immutable, so use the return value). Duration auto-carries seconds into minutes/hours, but Period does NOT carry days into months — use normalized() for years/months only.

solid answer

~40 s

Both types are immutable, so every operation returns a new instance. Construct a Duration with Duration.ofDays/ofHours/ofMinutes/ofSeconds/ofMillis/ofNanos, Duration.of(n, unit), or parse ISO-8601 with Duration.parse("PT90M"). Construct a Period with Period.ofYears/ofMonths/ofDays/of(y,m,d) or Period.parse("P1Y2M10D"). Combine with plus/minus (plusHours, plusDays, plus(otherAmount)), multiply with multipliedBy/negated. The key gotcha: a Duration is a single magnitude in seconds, so it always behaves as a normalized quantity (90 minutes is just 5400s; toHoursPart/toMinutesPart split it). A Period stores years, months, days as three independent ints and does NOT normalize across them: Period.ofDays(45) stays 45 days, not 1 month 15 days, because months are variable-length. Period.normalized() only normalizes months into years (13 months → 1 year 1 month); it never touches days. Also, adding a Duration and a Period together isn't directly supported — you apply each to a temporal, or keep them separate.

code

java · 8 lines
java
Duration d = Duration.ofMinutes(90);      // 5400s
Duration combined = d.plusSeconds(30).multipliedBy(2); // remember: assign!
System.out.println(combined.toMinutesPart()); // part accessor (Java 9+)

Period p = Period.ofWeeks(2);             // -> 14 DAYS, no weeks field
System.out.println(p.getDays());          // 14
System.out.println(Period.ofDays(45).normalized()); // P0Y0M45D (days untouched)
System.out.println(Period.of(0, 13, 0).normalized()); // P1Y1M0D

go deeper

for a junior

Can create a Duration/Period with the obvious factory and knows results must be assigned.

for a middle

Knows the ISO formats, that Period.ofWeeks becomes days, and that Period doesn't convert days into months while Duration is inherently normalized.

for a senior

Explains normalized()'s months-only behavior, the toXPart accessors, and that Period+Duration can't be merged but both apply to a temporal.

for a principal

Reasons about modeling amounts in domain code (storing/serializing ISO-8601, avoiding lossy normalization assumptions) and the API design rationale for keeping calendar and exact amounts separate and non-normalizing.

## Immutability first Every `Duration`/`Period` is **immutable and thread-safe**. Methods like `plusHours` return a *new* object; the original is unchanged. A classic bug is calling `d.plusHours(2);` and ignoring the result — you must assign it: `d = d.plusHours(2);`. ## Constructing a `Duration` ```java Duration.ofDays(2); // 48 hours Duration.ofHours(3); Duration.ofMinutes(90); // 5400 seconds Duration.ofSeconds(30, 500_000_000); // 30.5s (seconds + nanos) Duration.ofMillis(250); Duration.ofNanos(1_000); Duration.of(2, ChronoUnit.HOURS); Duration.parse("PT1H30M"); // ISO-8601: 1h30m (note the leading PT) Duration.between(t1, t2); // requires time-carrying temporals ``` ISO-8601 for durations uses `PnDTnHnMnS` (the `T` separates date-ish days from time): `PT90M`, `PT1H30M`, `P2DT3H`. ## Constructing a `Period` ```java Period.ofYears(1); Period.ofMonths(6); Period.ofWeeks(2); // stored as 14 DAYS (no weeks field!) Period.ofDays(10); Period.of(1, 2, 10); // 1y 2m 10d Period.parse("P1Y2M10D"); // ISO-8601 period (no T) Period.between(startDate, endDate); ``` Note `Period.ofWeeks(2)` is converted to **14 days** — `Period` has no weeks field; `getDays()` returns 14. ## Combining Both support fluent, chainable arithmetic (each returns a new value): ```java Duration total = Duration.ofHours(1).plusMinutes(30).plusSeconds(15); Duration twice = base.multipliedBy(2); Duration back = base.negated(); Duration sum = a.plus(b); Period p = Period.ofMonths(1).plusDays(15); Period q = Period.of(1,0,0).plus(Period.ofMonths(2)); // 1y 2m 0d ``` ### Mixing the two — not directly There is **no** single object that holds both calendar and exact time. You can't `period.plus(duration)`. Instead apply both to a temporal: `localDateTime.plus(period).plus(duration)`. (Older code sometimes reaches for `org.threeten` or a custom amount, but the JDK keeps them apart on purpose.) ## Normalization — the big gotcha ### Duration is always normalized A `Duration` is fundamentally one number of seconds (+nanos). `Duration.ofMinutes(90)` and `Duration.ofSeconds(5400)` are `equals`. To read it in parts (Java 9+): ```java Duration d = Duration.ofSeconds(3661); // 1h 1m 1s d.toHoursPart(); // 1 d.toMinutesPart(); // 1 d.toSecondsPart(); // 1 d.toHours(); // 1 (total hours) d.toMinutes(); // 61 (total minutes) ``` ### Period does NOT cross days↔months Because months/years are variable-length, `Period` keeps years/months/days as **independent** integers and will not convert days into months: ```java Period.ofDays(45).getMonths(); // 0 — stays 45 days Period.ofDays(45).getDays(); // 45 ``` `Period.normalized()` **only** rolls months into years and back-fills (it touches *years and months only*, never days): ```java Period.of(0, 13, 0).normalized(); // P1Y1M0D (13 months -> 1y 1m) Period.of(1, -1, 0).normalized(); // P0Y11M0D Period.of(0, 0, 45).normalized(); // P0Y0M45D — UNCHANGED, days untouched ``` So if you expected `Period.ofDays(45)` to mean "a month and a half," you'll be surprised — it can't, because the number of days in a month isn't fixed. ## Quick rules - Always use the return value (immutability). - `Duration.ofDays(1)` = 24h exactly; `Period.ofDays(1)` = a calendar day. - `Period.ofWeeks(n)` → days; no weeks/hours fields exist on Period. - `normalized()` on Period = years/months only; on Duration it's inherently normalized. - Don't try to merge a Period and a Duration into one amount — apply both to the temporal.

  • Why does Period.normalized() refuse to convert 45 days into 1 month 15 days?
    Because a month is variable-length (28–31 days), so days cannot be converted into months without a concrete anchor date. normalized() only rolls the fixed 12-months-per-year relationship between months and years.
  • How do you split a Duration into hours, minutes, and seconds for display?
    Use the Java 9+ part accessors: toHoursPart(), toMinutesPart(), toSecondsPart() (and toDaysPart/toMillisPart/toNanosPart), which give the component values rather than the totals from toHours()/toMinutes().

saying these in an interview costs you the question

  • Ignoring the return value of an immutable plus/minus call
  • Expecting Period.ofDays(45) to become 1 month 15 days
  • Thinking Period.normalized() converts days into months
  • Assuming Period has weeks/hours fields, or that you can add Period+Duration

context