Topic 12.5
Dates and Times with java.time
In one line
java.time (Java 8) replaced the old, error-prone Date and Calendar with clear immutable types: LocalDate and LocalDateTime for calendar values, Instant for a moment on the timeline, ZonedDateTime for a moment in a particular time zone, and Duration/Period for amounts of time.
Think of it like this
A birthday, a train ticket and a stopwatch. Your birthday is just a date: it doesn't matter which city you're in (LocalDate). A flight departure happens at one exact moment for the whole world, but the clock on the wall shows a different time in Mumbai and New York (Instant plus a ZoneId gives a ZonedDateTime). A stopwatch measures how long something took, in seconds (Duration), while "3 months and 2 days" is a calendar amount (Period).
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Time zone
- A region's rules for its clocks, like Asia/Kolkata or America/New_York, including when daylight saving starts and ends.
- UTC
- Coordinated Universal Time: the world's reference clock. Other zones are written as an offset from it, like +05:30.
- Offset
- How far a local clock is ahead of or behind UTC at a given moment, like +05:30 or -04:00.
- Daylight saving time (DST)
- Moving clocks forward in spring and back in autumn in some countries. India doesn't use it; the USA does.
- Instant
- One exact moment on the world timeline, the same everywhere.
- Local date-time
- A calendar date and clock time with no zone attached, like what's written in a diary.
- Duration
- An exact amount of time, like 90 minutes.
- Period
- A calendar amount, like 2 months and 3 days, whose length in seconds depends on which months.
Step by step
01Why the old API was replaced
java.util.Date was mutable (anyone holding it could change it), its toString used the computer's time zone, years started at 1900 and months at 0. SimpleDateFormat wasn't thread-safe. Calendar was verbose and had the same 0-based months.
JSR 310, based on the Joda-Time library, gave Java 8 a new package where every type is immutable, each concept has its own type, and names say what they mean.
02Pick the right type
Ask: is this a calendar value people write down (a birthday: LocalDate), a moment (a payment's timestamp: Instant), or a moment as seen in a place (a meeting at 10:00 in Mumbai: ZonedDateTime)?
Store moments as Instant (or UTC) and convert to a zone only to display them. Store "10:00 every Monday in the Pune office" as LocalTime plus ZoneId, because the matching instants change when DST rules change.
03Immutable: every change makes a new object
LocalDate d = LocalDate.of(2026, 3, 15); d.plusDays(1); leaves d unchanged: plusDays returns a new date and the code throws it away. Write d = d.plusDays(1);.
Immutability is what makes these types safe to share between threads and to use as map keys.
04Calendar maths has rules
Adding a month to January 31st gives the last day of February, not March 3rd. Period.between counts whole years, then months, then days, so 2000-05-20 to 2026-03-15 is 25 years, 9 months and 23 days.
Leap years: divisible by 4, except centuries, except centuries divisible by 400. So 2024 and 2000 are leap years, but 2100 is not. Year.isLeap() and LocalDate.isLeapYear() know this.
05Zones, offsets and daylight saving
The same Instant is 15:00 in Mumbai (+05:30) and 05:30 in New York (-04:00 on 8 March 2026, when the USA has just moved to summer time). Converting between zones never changes the instant, only how it's displayed.
On 8 March 2026 New York clocks jump from 02:00 to 03:00, so 02:30 doesn't exist. ZonedDateTime.of moves a non-existent time forward by the length of the gap (to 03:30) instead of failing, and that day is 23 hours long.
06Formatting and parsing
LocalDate.parse("05/10/2026", DateTimeFormatter.ofPattern("dd/MM/yyyy")) reads a date; date.format(...) writes one. Parsing is strict about the pattern: "5/10/2026" fails with dd, which needs two digits (use d to allow one).
Text like month and day names depends on the Locale. Pass one explicitly (ofPattern("d MMMM yyyy", Locale.ENGLISH)) so output doesn't change with the computer's settings.
07Testable code: inject a Clock
LocalDate.now() reads the system clock, which makes code hard to test (the answer changes every day). Pass a java.time.Clock into your class and call LocalDate.now(clock); tests use Clock.fixed(...) to freeze time.
Try it yourself
- 1
End of month again
In the first example, change
todaytoLocalDate.of(2024, 1, 31)and run it. February 2024 has 29 days, so one month later is 2024-02-29. - 2
Pick another city
In "One instant, two cities", add
Asia/Tokyo(UTC+9, no DST). Predict the time before running: 09:30Z plus 9 hours is 18:30.
Code & diagrams
Expected output
exam: 2026-03-15 (SUNDAY)
days to go: 43
one month after Jan 31: 2026-02-28
2024 leap year? true, 2100? false
age on exam day: 25 years 9 months 23 daysExpected output
Mumbai: 2026-03-08 15:00 +05:30
New York: 2026-03-08 05:30 -04:00
same instant? trueExpected output
2026-03-08T03:30-04:00[America/New_York]
hours in that day: 23Expected output
2026-10-05 is a MONDAY
2026-10-05
2026-10-31 is the last day of the monthclass Greeter {
private final Clock clock;
Greeter(Clock clock) { this.clock = clock; }
String greet() {
int hour = LocalTime.now(clock).getHour();
return hour < 12 ? "Good morning" : "Good afternoon";
}
}
// production: new Greeter(Clock.systemDefaultZone())
// test: new Greeter(Clock.fixed(Instant.parse("2026-01-01T03:00:00Z"), ZoneOffset.UTC))Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forgetting that dates are immutable
Write LocalDate d = LocalDate.of(2026, 3, 15); d.plusDays(1); System.out.println(d);.
Break #2
Parsing with the wrong pattern
Parse "2026-10-05" with DateTimeFormatter.ofPattern("dd/MM/yyyy").
Myth vs fact
Myth
A day is always 24 hours.
Fact
In zones with daylight saving, one day a year has 23 hours and one has 25. Use ZonedDateTime and Duration.between, not hours times 24.
Myth
LocalDateTime is fine for timestamps.
Fact
It has no zone, so it can't say which moment it was. Use Instant (or a UTC time) for events and logs.
Myth
YYYY in a pattern means the year.
Fact
YYYY is the week-based year, which differs from the calendar year in the last days of December and first days of January. Use yyyy (or uuuu).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Time-zone rules come from the IANA tz database bundled in the JDK (
tzdb.dat). Governments change rules often, so long-running services need JDK updates (or the TZUpdater tool) to keep conversions correct. - ▸
Store future appointments as
LocalDateTimeplusZoneId, not as anInstant: if a country changes its DST rules before the date, the wall-clock time people agreed on stays correct while the instant moves. - ▸
Instanthas nanosecond fields, butInstant.now()resolution depends on the OS clock (often microseconds). For measuring elapsed time inside a program useSystem.nanoTime(), which is monotonic; wall clocks can jump when NTP adjusts them. - ▸
For overlapping wall-clock times (autumn),
ZonedDateTimepicks the earlier offset by default;withLaterOffsetAtOverlap()andwithEarlierOffsetAtOverlap()choose explicitly.
Remember this
- 1
Local types have no time zone:
LocalDate(2026-03-15),LocalTime(09:30),LocalDateTime(2026-03-15T09:30). Use them for birthdays, opening hours and things people write on a calendar. They can't tell you the exact moment, because 09:30 happens at different moments in different places. - 2
**
Instantis a point on the global timeline, stored as seconds and nanoseconds since 1970-01-01T00:00Z (UTC). Use it for timestamps: logs, created-at columns, event times.ZonedDateTime** combines a local date-time with aZoneIdsuch asAsia/Kolkata, and knows that zone's rules, including daylight saving time (DST).OffsetDateTimehas a fixed offset like+05:30but no DST rules. - 3
All
java.timetypes are immutable and thread-safe. Methods likeplusDaysreturn a new object;date.plusDays(1);on its own line does nothing useful. Months are numbered 1 to 12 (the oldCalendarused 0 to 11, a famous source of bugs). - 4
Calendar maths is not simple maths.
LocalDate.of(2026, 1, 31).plusMonths(1)is 2026-02-28 (clamped to the month's last day). On the day a zone switches to summer time, one local hour doesn't exist (it's skipped) and a day is only 23 hours long; in autumn one local hour happens twice.ZonedDateTimehandles this; plain arithmetic on hours does not. - 5
**
Durationis an exact amount of time in seconds and nanoseconds (2 hours 30 minutes).Period** is a calendar amount in years, months and days (1 year 2 months).ChronoUnit.DAYS.between(a, b)counts whole units between two values. - 6
Formatting and parsing use **
DateTimeFormatter**: predefined ones likeISO_LOCAL_DATE, or patterns like"dd/MM/yyyy". Formatters are immutable and thread-safe, unlike the oldSimpleDateFormat, which broke when shared between threads. Be careful with pattern letters:MMis month andmmis minutes;yyyyis year-of-era andYYYYis week-based year, which gives wrong years around New Year.
Explain it without notes
When do you use LocalDate, LocalDateTime, Instant and ZonedDateTime?
What is the difference between Duration and Period?
Why was java.util.Date replaced, and why is java.time safer?
Practice
Print how many days are left until 31 December 2026 from 5 October 2026.
Given a flight that departs Mumbai on 2026-11-20 at 23:15 local time and lasts 9 hours 40 minutes, print the arrival time in London.
Print the dates of every Monday in November 2026.
Trade-offs
- ↔
Instant is unambiguous for storage but needs a zone to be shown to people; zoned types are human-friendly but depend on changing zone rules.
- ↔
Local types are simple but can't represent a moment; using them for timestamps causes bugs as soon as two zones are involved.
Done when you can
Done when you can pick between LocalDate, LocalDateTime, Instant and ZonedDateTime for a given requirement.
Done when you remember that java.time types are immutable and keep the results of plus and minus methods.
Done when you can explain the DST gap and overlap and why a day isn't always 24 hours.
Done when you can parse and format with DateTimeFormatter and avoid the YYYY and mm traps.
Done when you can make time-dependent code testable with Clock.