How do you construct, combine, and normalize Duration and Period amounts, and what are the gotchas?
answer
- Immutable → must assign the result of plus/minus
- Duration ISO = PT1H30M (T present); Period ISO = P1Y2M10D (no T)
- Period.ofWeeks(2) → 14 days (no weeks field)
- Period.normalized() rolls months↔years only, never days
- Can't add a Period and a Duration directly — apply both to a temporal
basics
~20 sBuild 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 sBoth 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 linesDuration 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()); // P1Y1M0Dgo deeper
Can create a Duration/Period with the obvious factory and knows results must be assigned.
Knows the ISO formats, that Period.ofWeeks becomes days, and that Period doesn't convert days into months while Duration is inherently normalized.
Explains normalized()'s months-only behavior, the toXPart accessors, and that Period+Duration can't be merged but both apply to a temporal.
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