11.2 Timestamps, Intervals, and Durations: `Instant`, `Period`, and `Duration`
Key Takeaways
Instant models a universal, machine-based point on the timeline representing seconds and nanoseconds offset from the Unix epoch (1970-01-01T00:00:00Z) in UTC.
Period encapsulates calendar-based amounts of years, months, and days; adding a Period to a LocalTime throws UnsupportedTemporalTypeException, and an Instant accepts only day-based periods (months or years throw).
Duration encapsulates exact machine-based amounts of time in seconds and nanoseconds (accessible as days, hours, minutes, seconds, nanos); it cannot be applied to LocalDate.
Methods on Period and Duration such as ofYears(), ofMonths(), and ofDays() are static factories that do not chain; chaining them resolves only the final static method call.
ChronoUnit.between() computes the whole elapsed units between two temporal objects, returning positive values if the end object is later than the start object and negative values if earlier.
11.2 Timestamps, Intervals, and Durations: Instant, Period, and Duration
Working effectively with time requires distinguishing between two distinct perspectives: human calendar time (years, months, weeks, days) and machine timeline points and durations (exact seconds, milliseconds, and nanoseconds). The Java SE 17 Date-Time API models these dimensions using Instant, Period, and Duration.
Machine Timeline: Instant
An Instant represents an instantaneous point on the global timeline, defined as an offset from the standard Unix epoch: January 1, 1970, 00:00:00 UTC (Zulu time). It stores time as a 64-bit integer representing epoch seconds and a 32-bit integer representing nanoseconds of the second.
Factory Methods and Conversions
// Current instant from system clock in UTC
Instant now = Instant.now();
// Specific offsets from Unix epoch
Instant epoch = Instant.ofEpochMilli(0); // 1970-01-01T00:00:00Z
Instant specificSec = Instant.ofEpochSecond(1_000_000_000); // 2001-09-09T01:46:40Z
// Parsing standard ISO-8601 UTC string (must include 'Z' or offset)
Instant parsed = Instant.parse("2026-10-11T12:00:00Z");
Characteristics and Limitations of Instant
Because Instant models the continuous physical timeline in UTC without human calendar semantics:
- It contains no time zone (it is inherently pinned to UTC).
- It has no concept of year, month, day, or day of week.
- Calling
instant.get(ChronoField.YEAR)orinstant.get(ChronoField.DAY_OF_MONTH)compiles (sinceInstantimplementsTemporalAccessor), but throwsUnsupportedTemporalTypeExceptionat runtime!
To bridge human calendar representations with machine timestamps, you must associate the Instant with a time zone (ZoneId) to produce a ZonedDateTime:
Instant stamp = Instant.now();
ZonedDateTime tokyoTime = stamp.atZone(ZoneId.of("Asia/Tokyo"));
LocalDate localDateInTokyo = tokyoTime.toLocalDate();
Human Calendar Amounts: Period
A Period represents an amount of time in terms of calendar-based units: years, months, and days.
Factory Creation and Parsing
Period instances are created using static factory methods:
Period oneYear = Period.ofYears(1);
Period twoMonths = Period.ofMonths(2);
Period threeWeeks = Period.ofWeeks(3); // Stored internally as 21 days!
Period fiveDays = Period.ofDays(5);
// Compound period: 1 year, 6 months, and 15 days
Period compound = Period.of(1, 6, 15);
// Parsing ISO-8601 period format (prefixed with 'P')
Period parsedPeriod = Period.parse("P1Y6M15D"); // 1 year, 6 months, 15 days
Period twoWeeks = Period.parse("P2W"); // 14 days
Modifying Temporals with Period
Period is designed to be added to or subtracted from date-bearing temporal objects (LocalDate, LocalDateTime, ZonedDateTime):
LocalDate wedding = LocalDate.of(2026, Month.JUNE, 20);
LocalDate anniversary = wedding.plus(Period.ofYears(1)); // 2027-06-20
The Period Incompatibility Trap
A Period contains days, months, or years. It contains no hours, minutes, or seconds. If you attempt to add or subtract a Period from a LocalTime, the code compiles (because plus(TemporalAmount) accepts any TemporalAmount), but throws UnsupportedTemporalTypeException at runtime:
LocalTime bedtime = LocalTime.of(22, 0);
Period dayPeriod = Period.ofDays(1);
// COMPILES! But throws UnsupportedTemporalTypeException at runtime:
// java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Days
LocalTime failed = bedtime.plus(dayPeriod);
Machine Time Amounts: Duration
A Duration represents an amount of time in terms of exact, machine-based units: seconds and nanoseconds. For convenience, the API provides helper methods to work with days, hours, and minutes, but internally these are always converted to exact seconds ().
Factory Creation and Calculation
Duration oneDay = Duration.ofDays(1); // 86,400 seconds
Duration twoHours = Duration.ofHours(2); // 7,200 seconds
Duration tenMinutes = Duration.ofMinutes(10); // 600 seconds
Duration thirtySeconds = Duration.ofSeconds(30);
Duration hundredMillis = Duration.ofMillis(100);
// Calculating elapsed duration between two temporal points
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(10, 30);
Duration elapsed = Duration.between(start, end); // PT1H30M (1 hour, 30 minutes)
// Parsing ISO-8601 duration format (prefixed with 'PT' for time)
Duration parsedDuration = Duration.parse("PT2H15M"); // 2 hours, 15 minutes
The Duration Incompatibility Trap
A Duration tracks time in seconds and nanoseconds. A LocalDate contains only calendar dates and has no time components. If you attempt to add or subtract a Duration to or from a LocalDate, Java throws UnsupportedTemporalTypeException at runtime:
LocalDate date = LocalDate.of(2026, Month.MAY, 1);
Duration twoHoursDuration = Duration.ofHours(2);
// COMPILES! But throws UnsupportedTemporalTypeException at runtime:
// java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
LocalDate invalidDate = date.plus(twoHoursDuration);
Warning
Even Duration.ofDays(1) throws an exception when added to a LocalDate! Although Duration has an ofDays() factory, it translates days into seconds (). Because LocalDate cannot accept seconds, date.plus(Duration.ofDays(1)) immediately throws UnsupportedTemporalTypeException.
Comparison: Period vs. Duration
| Feature | Period | Duration |
|---|---|---|
| Conceptual Domain | Human calendar intervals | Machine timeline intervals |
| Units Supported | Years, Months, Days | Seconds, Nanoseconds (Days, Hours, Minutes) |
| Day Definition | Variable (23, 24, or 25 hours depending on DST) | Fixed ( seconds exactly) |
| Compatible Types | LocalDate, LocalDateTime, ZonedDateTime | LocalTime, LocalDateTime, Instant, ZonedDateTime |
| Incompatible Types | LocalTime (throws); Instant accepts only day-based periods, so months or years throw | LocalDate (throws exception) |
| ISO-8601 Format | Starts with P (e.g., P1Y2M3D) | Starts with PT or P...T... (e.g., PT1H30M) |
The Static Factory Chaining Trap
A well-known trick pattern is the static method chaining illusion on Period and Duration.
In Java, static methods can be syntactically called on an instance reference or chained together, but static calls are resolved entirely by the compiler against the declaring class. They never operate on an accumulated instance:
// What does this produce?
Period period = Period.ofYears(1).ofMonths(2).ofDays(3);
System.out.println(period); // PRINTS: P3D (3 days only!)
Why Does This Occur?
Period.ofYears(1)executes statically, returning a newPeriodrepresenting 1 year (P1Y)..ofMonths(2)is invoked. BecauseofMonthsisstatic, the compiler ignores theP1Yinstance completely and evaluatesPeriod.ofMonths(2), returning a newPeriodof 2 months (P2M)..ofDays(3)is invoked. Similarly, it isstatic, discarding theP2Minstance and returning a newPeriodof 3 days (P3D).- The variable
periodreceives only the 3-day period!
// WRONG: Yields only 30 minutes!
Duration d = Duration.ofHours(5).ofMinutes(30);
// CORRECT way to create compound periods and durations:
Period correctPeriod = Period.of(1, 2, 3); // 1 year, 2 months, 3 days
Duration correctDuration = Duration.ofHours(5).plusMinutes(30); // 5 hours, 30 minutes
Calculating Differences with ChronoUnit.between()
The java.time.temporal.ChronoUnit enum represents standard date-time units (from NANOS through FOREVER) and provides the between(Temporal, Temporal) utility method:
LocalDate d1 = LocalDate.of(2026, Month.JANUARY, 1);
LocalDate d2 = LocalDate.of(2026, Month.JANUARY, 15);
long daysBetween = ChronoUnit.DAYS.between(d1, d2); // 14 (d1 inclusive, d2 exclusive)
Key Rules for ChronoUnit.between()
- Truncation to Whole Units:
between()calculates the number of complete whole units. Any fractional remainder is truncated. For example, between 10:00 and 11:45,ChronoUnit.HOURS.between()yields1. - Direction and Sign: The calculation is
temporal2Exclusive - temporal1Inclusive. If the second argument is chronologically earlier than the first, the result is negative:LocalTime t1 = LocalTime.of(15, 0); LocalTime t2 = LocalTime.of(12, 0); long hours = ChronoUnit.HOURS.between(t1, t2); // -3 - Unit Compatibility: Attempting to calculate an unsupported unit (such as hours between two
LocalDateinstances) throwsUnsupportedTemporalTypeException:// COMPILES! But throws UnsupportedTemporalTypeException at runtime: long hours = ChronoUnit.HOURS.between(d1, d2);
What is the output of the following code snippet?
LocalDate start = LocalDate.of(2026, Month.JANUARY, 1);
Period period = Period.ofYears(2).ofMonths(3).ofDays(10);
LocalDate end = start.plus(period);
System.out.println(end);
2028-04-11
2026-01-11
2028-01-11
2026-04-11
Consider the following code snippet:
LocalDate date = LocalDate.of(2026, Month.JULY, 4);
Duration duration = Duration.ofDays(2);
LocalDate result = date.plus(duration);
System.out.println(result);
What is the result of attempting to compile and run this code?
Compilation fails because plus(TemporalAmount) does not accept Duration
Prints 2026-07-06
Throws a DateTimeException because Duration requires a time zone
Throws an UnsupportedTemporalTypeException at runtime
Consider the following code snippet:
LocalTime meetingStart = LocalTime.of(14, 0);
LocalTime meetingEnd = LocalTime.of(16, 30);
long diffHours = ChronoUnit.HOURS.between(meetingStart, meetingEnd);
long diffMinutes = ChronoUnit.MINUTES.between(meetingEnd, meetingStart);
System.out.println(diffHours + " " + diffMinutes);
What does this code output?
2 -150
Throws an UnsupportedTemporalTypeException at runtime
2.5 -150
2 150
Sections you finish are checked off in the contents.