ZonedDateTime, ZoneId and Instant

Naveen AnamaneniUpdated · 6 min read

When people in different countries share a moment, such as a video meeting, a local date and time isn't enough: 9:00 in Pune and 9:00 in London are different moments. The java.time package has three classes for this. ZoneId names a time zone, such as Asia/Kolkata. ZonedDateTime is a date and time in a specific zone, including its daylight-saving rules. Instant is an exact moment on the global timeline, counted in UTC, with no zone at all. In this lesson, you will learn all three and build a small meeting scheduler.

The Classes

ClassHoldsExample
ZoneIdA time zone name and its rulesAsia/Kolkata, Europe/London
ZoneOffsetA fixed difference from UTC+05:30
ZonedDateTimeDate, time and zone2026-11-12T09:00+05:30[Asia/Kolkata]
OffsetDateTimeDate, time and a fixed offset2026-11-12T09:00+05:30
InstantA moment in UTC (seconds and nanoseconds since 1970)2026-11-12T03:30:00Z
One moment, three wall clocks

Methods

MethodDescription
ZoneId.of("Asia/Kolkata")It is used to get a time zone by its region name.
ZonedDateTime.of(localDateTime, zone)It attaches a zone to a local date and time.
zdt.withZoneSameInstant(zone)It shows the same moment in another zone.
zdt.withZoneSameLocal(zone)It keeps the same clock time but changes the zone (a different moment).
zdt.toInstant()It converts to an Instant.
instant.atZone(zone)It shows an Instant in a zone.
Instant.now(), Instant.ofEpochSecond(s)It gets the current moment, or a moment from epoch seconds.

Example: The Same Moment in Three Zones

Let's see an example that shows a meeting time around the world.

Zones.java▶ Try it
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
 
public class Zones {
    public static void main(String[] args) {
        ZoneId india = ZoneId.of("Asia/Kolkata");
        ZonedDateTime meeting = ZonedDateTime.of(
            LocalDateTime.of(2026, 11, 12, 19, 0), india);
 
        System.out.println("India:    " + meeting);
        System.out.println("London:   "
            + meeting.withZoneSameInstant(ZoneId.of("Europe/London")));
        System.out.println("New York: "
            + meeting.withZoneSameInstant(ZoneId.of("America/New_York")));
 
        Instant moment = meeting.toInstant();
        System.out.println("Instant:  " + moment);
        System.out.println("Epoch seconds: " + moment.getEpochSecond());
    }
}
Output
India:    2026-11-12T19:00+05:30[Asia/Kolkata]
London:   2026-11-12T13:30Z[Europe/London]
New York: 2026-11-12T08:30-05:00[America/New_York]
Instant:  2026-11-12T13:30:00Z
Epoch seconds: 1794490200

Explanation: withZoneSameInstant keeps the moment and changes how it is shown: 19:00 in India is 13:30 in London and 08:30 in New York. The Instant prints in UTC (the Z means "zero offset"); London in November also has zero offset. All four lines describe the same moment.

Daylight Saving Time

Many zones move their clocks forward in spring and back in autumn. ZonedDateTime knows these rules, so arithmetic stays correct:

Dst.java▶ Try it
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
 
public class Dst {
    public static void main(String[] args) {
        ZoneId ny = ZoneId.of("America/New_York");
 
        ZonedDateTime before = ZonedDateTime.of(
            LocalDateTime.of(2026, 3, 8, 1, 30), ny);
        ZonedDateTime oneHourLater = before.plusHours(1);
        System.out.println(before + " + 1 hour = " + oneHourLater);
 
        ZonedDateTime gap = ZonedDateTime.of(
            LocalDateTime.of(2026, 3, 8, 2, 30), ny);
        System.out.println("2:30 on that day becomes " + gap);
 
        ZonedDateTime dayLater = before.plusDays(1);
        System.out.println("plusDays(1) is "
            + Duration.between(before, dayLater).toHours() + " hours");
    }
}
Output
2026-03-08T01:30-05:00[America/New_York] + 1 hour = 2026-03-08T03:30-04:00[America/New_York]
2:30 on that day becomes 2026-03-08T03:30-04:00[America/New_York]
plusDays(1) is 23 hours

Explanation: On 8 March 2026, New York clocks jump from 2:00 to 3:00. So one hour after 1:30 is 3:30, the time 2:30 doesn't exist and is moved to 3:30, and that "day" is only 23 hours long. plusDays keeps the same clock time; plusHours adds real elapsed time.

The Checkpoint: A Meeting Scheduler

This program schedules a meeting in Pune and shows it for colleagues in two other cities, warning when it falls outside working hours (9:00 to 18:00) for anyone.

Scheduler.java▶ Try it
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.TreeMap;
 
public class Scheduler {
    static final Map<String, ZoneId> TEAM = new TreeMap<>(Map.of(
        "Navika (Pune)", ZoneId.of("Asia/Kolkata"),
        "Srihitha (London)", ZoneId.of("Europe/London"),
        "Advitha (Sydney)", ZoneId.of("Australia/Sydney")));
 
    static void show(LocalDateTime puneTime) {
        ZonedDateTime meeting = puneTime.atZone(ZoneId.of("Asia/Kolkata"));
        System.out.println("Meeting at " + puneTime + " Pune time:");
        TEAM.forEach((name, zone) -> {
            LocalTime local = meeting.withZoneSameInstant(zone).toLocalTime();
            boolean ok = !local.isBefore(LocalTime.of(9, 0))
                      && !local.isAfter(LocalTime.of(18, 0));
            System.out.println("  " + name + ": " + local
                + (ok ? "" : "  (outside working hours)"));
        });
    }
 
    public static void main(String[] args) {
        show(LocalDateTime.of(2026, 11, 12, 14, 0));
        show(LocalDateTime.of(2026, 11, 12, 10, 0));
    }
}
Output
Meeting at 2026-11-12T14:00 Pune time:
  Advitha (Sydney): 19:30  (outside working hours)
  Navika (Pune): 14:00
  Srihitha (London): 08:30  (outside working hours)
Meeting at 2026-11-12T10:00 Pune time:
  Advitha (Sydney): 15:30
  Navika (Pune): 10:00
  Srihitha (London): 04:30  (outside working hours)

Explanation: For each team member, the meeting moment is converted with withZoneSameInstant, then its local time is checked against 9:00 to 18:00. Neither slot suits all three cities, which is exactly the kind of fact a scheduler should point out.

Common Mistakes

Storing LocalDateTime for events shared across zones. Store an Instant (or ZonedDateTime) instead.

Using withZoneSameLocal when converting. It changes the moment; use withZoneSameInstant.

Assuming a day is always 24 hours. Daylight-saving days can be 23 or 25 hours.

Interview Corner

What is the difference between Instant, LocalDateTime and ZonedDateTime?

Instant is an exact point on the timeline in UTC, ideal for timestamps and storage. LocalDateTime is a date and time with no zone, so it doesn't identify a moment by itself. ZonedDateTime combines a local date-time with a ZoneId and its rules, so it identifies an exact moment and shows it as people in that zone see it, handling daylight saving. Convert between them with atZone, toInstant and withZoneSameInstant.

Practice

  1. Easy: Print the current time in Tokyo, Dubai and Los Angeles using ZonedDateTime.now(zone).
  2. Harder: Extend the scheduler to find, for a given day, every whole hour in Pune time that falls within working hours for all three cities.

Quick check: Zones and instants

4 questions
1What does Instant represent?
2Which method shows the same moment in another zone?
3Why prefer "Europe/London" over "BST"?
4How long can a day be in a zone with daylight saving?

Key Takeaways

Was this tutorial helpful?

Lesson 331 of 482