ZonedDateTime

Su Suriya Ravichandran Updated 14 Nov 2026
6 min read ·Lesson 117 of 133

java.time.ZonedDateTime is a date and time in a specific time zone, such as 2026-11-20T19:30+05:30[Asia/Kolkata]. It combines a LocalDateTime with a ZoneId and the zone's current offset from UTC, so it identifies one exact moment while still knowing the local wall-clock time. It is the right type for anything scheduled for a place (a meeting in Chennai, a flight departing London, a sale ending at midnight in New York), and the only java.time type whose arithmetic follows daylight-saving rules.

Time Zones and Offsets

Two related ideas are easy to confuse:

ZoneOffsetZoneId
Example+05:30, -05:00, ZAsia/Kolkata, America/New_York
MeaningA fixed difference from UTCA region with rules for its offset over time
ChangesNeverCan differ by season (DST) and by historical or future law

America/New_York is -05:00 in winter and -04:00 in summer. Europe/London is Z (UTC+0) in winter and +01:00 in summer. Asia/Kolkata is +05:30 all year. A ZoneId contains these rules, using the IANA time zone database shipped with the JDK (about 600 zone IDs).

Use region IDs like Asia/Kolkata, not abbreviations such as IST, which are ambiguous (IST also means Irish and Israel Standard Time).

Creating a ZonedDateTime

import java.time.*;

ZoneId kolkata = ZoneId.of("Asia/Kolkata");

ZonedDateTime now = ZonedDateTime.now(kolkata);
ZonedDateTime webinar = ZonedDateTime.of(2026, 11, 20, 19, 30, 0, 0, kolkata);
ZonedDateTime fromLocal = LocalDateTime.of(2026, 11, 20, 19, 30).atZone(kolkata);
ZonedDateTime parsed = ZonedDateTime.parse("2026-11-20T19:30+05:30[Asia/Kolkata]");
ZonedDateTime fromInstant = Instant.parse("2026-11-20T14:00:00Z").atZone(kolkata);
webinar.getOffset();          // +05:30
webinar.getZone();            // Asia/Kolkata
webinar.toInstant();          // 2026-11-20T14:00:00Z
webinar.toLocalDateTime();    // 2026-11-20T19:30
webinar.toOffsetDateTime();   // 2026-11-20T19:30+05:30

Converting Between Zones

Two methods change the zone, with very different meanings:

ZonedDateTime ist = ZonedDateTime.of(2026, 11, 20, 19, 30, 0, 0, ZoneId.of("Asia/Kolkata"));

ist.withZoneSameInstant(ZoneId.of("Europe/London"));   // 2026-11-20T14:00Z[Europe/London]
ist.withZoneSameLocal(ZoneId.of("Europe/London"));     // 2026-11-20T19:30Z[Europe/London]
MethodKeepsChangesUse for
withZoneSameInstantThe momentThe wall-clock time"What time is this meeting for my colleague in London?"
withZoneSameLocalThe wall-clock timeThe moment"Same local time, but in another city" (rarely what you want)

Displaying a single event to users in different countries always uses withZoneSameInstant (or instant.atZone(zone)).

Daylight-Saving Time

DST makes local time discontinuous twice a year. ZonedDateTime handles both cases using the zone's rules.

The spring gap

In New York, at 2:00 am on 8 March 2026, clocks jump to 3:00 am. Times from 2:00 to 2:59 do not exist that day:

ZoneId ny = ZoneId.of("America/New_York");
ZonedDateTime.of(LocalDateTime.of(2026, 3, 8, 2, 30), ny);
// 2026-03-08T03:30-04:00[America/New_York]: shifted forward by the length of the gap

Measuring across the gap gives the true elapsed time:

ZonedDateTime before = ZonedDateTime.of(LocalDateTime.of(2026, 3, 8, 1, 0), ny);   // -05:00
ZonedDateTime after = ZonedDateTime.of(LocalDateTime.of(2026, 3, 8, 4, 0), ny);    // -04:00
Duration.between(before, after);      // PT2H: only two real hours passed

The same calculation with LocalDateTime returns three hours.

The autumn overlap

On 1 November 2026, New York clocks go back from 2:00 to 1:00, so 1:30 occurs twice. ZonedDateTime chooses the earlier offset by default, and you can request the later one:

ZonedDateTime first = ZonedDateTime.of(LocalDateTime.of(2026, 11, 1, 1, 30), ny);
// 2026-11-01T01:30-04:00[America/New_York]
first.withLaterOffsetAtOverlap();
// 2026-11-01T01:30-05:00[America/New_York]: one hour later in real time

Adding days versus adding hours

before.plusDays(1);      // 2026-03-09T01:00-04:00: same wall-clock time tomorrow
before.plusHours(24);    // 2026-03-09T02:00-04:00: exactly 24 hours later

Across a DST change, "one day later" and "24 hours later" are different. plusDays keeps the local time (right for "the same meeting tomorrow"); plusHours(24) keeps the exact duration (right for "this token expires in 24 hours").

India (Asia/Kolkata) does not observe DST, but any system with users or servers in other countries must handle these rules, which is exactly what ZonedDateTime does.

ZonedDateTime Versus OffsetDateTime and Instant

TypeContainsBest for
InstantA moment in UTCStoring and comparing when things happened
OffsetDateTimeLocal date-time + fixed offsetExchanging timestamps in APIs, databases (TIMESTAMP WITH TIME ZONE)
ZonedDateTimeLocal date-time + offset + zone rulesScheduling, user display, calendar arithmetic across DST

A typical architecture stores Instant or OffsetDateTime values in UTC, and converts to ZonedDateTime in the user's zone for display and for date arithmetic that must respect local rules.

Comparing ZonedDateTimes

isBefore, isAfter, and isEqual compare the instant, so values in different zones compare correctly:

ZonedDateTime a = ZonedDateTime.parse("2026-11-20T19:30+05:30[Asia/Kolkata]");
ZonedDateTime b = ZonedDateTime.parse("2026-11-20T14:00Z[Europe/London]");

a.isEqual(b);     // true: the same moment
a.equals(b);      // false: equals also compares the local date-time and zone

Use isEqual (or compare toInstant() values) to ask "is this the same moment?".

Practical Example: Finding a Meeting Time Across Zones

A team is spread across Chennai and London, and possibly New York. A meeting slot must fall within 9:00–18:00 local time on a weekday for every member.

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class Meeting {
    static final LocalTime WORK_START = LocalTime.of(9, 0);
    static final LocalTime WORK_END = LocalTime.of(18, 0);

    static boolean withinWorkHours(ZonedDateTime start, Duration length, ZoneId zone) {
        ZonedDateTime s = start.withZoneSameInstant(zone);
        ZonedDateTime e = s.plus(length);
        DayOfWeek d = s.getDayOfWeek();
        return d != DayOfWeek.SATURDAY && d != DayOfWeek.SUNDAY
                && !s.toLocalTime().isBefore(WORK_START)
                && !e.toLocalTime().isAfter(WORK_END)
                && s.toLocalDate().equals(e.toLocalDate());
    }

    static List<ZonedDateTime> commonSlots(LocalDate date, List<ZoneId> team, Duration length) {
        List<ZonedDateTime> options = new ArrayList<>();
        ZonedDateTime t = date.atStartOfDay(ZoneOffset.UTC);
        ZonedDateTime end = t.plusDays(1);
        for (; t.isBefore(end); t = t.plusMinutes(30)) {
            final ZonedDateTime slot = t;
            if (team.stream().allMatch(z -> withinWorkHours(slot, length, z))) options.add(slot);
        }
        return options;
    }

    public static void main(String[] args) {
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("HH:mm");
        LocalDate date = LocalDate.of(2026, 11, 17);
        List<ZoneId> team = List.of(ZoneId.of("Asia/Kolkata"), ZoneId.of("Europe/London"));
        List<ZonedDateTime> slots = commonSlots(date, team, Duration.ofMinutes(30));
        System.out.println(slots.size() + " slots for Chennai + London on " + date + ":");
        for (ZonedDateTime s : List.of(slots.get(0), slots.get(slots.size() - 1))) {
            System.out.println("  Chennai " + s.withZoneSameInstant(team.get(0)).format(fmt)
                    + "  London " + s.withZoneSameInstant(team.get(1)).format(fmt) + "  (UTC " + s.format(fmt) + ")");
        }
        List<ZoneId> withNy = new ArrayList<>(team);
        withNy.add(ZoneId.of("America/New_York"));
        System.out.println("Adding New York: " + commonSlots(date, withNy, Duration.ofMinutes(30)).size() + " slots");
    }
}

Output:

7 slots for Chennai + London on 2026-11-17:
  Chennai 14:30  London 09:00  (UTC 09:00)
  Chennai 17:30  London 12:00  (UTC 12:00)
Adding New York: 0 slots

The program steps through the day in UTC, half an hour at a time, and converts each candidate moment into every member's zone with withZoneSameInstant. A slot qualifies only if, in each zone, it falls on a weekday, starts at or after 9:00, ends by 18:00, and does not run past midnight. In November, London is five and a half hours behind Chennai, so working hours overlap from 09:00 to 12:30 UTC, giving seven half-hour slots, the first and last of which are shown. New York's working day (14:00–23:00 UTC in November) does not overlap with Chennai's (03:30–12:30 UTC) at all, so adding it leaves no common slot. Because offsets come from each zone's rules, the same code gives correct results in summer, when London and New York shift their clocks and the overlaps change.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about ZonedDateTime and get answers drawn from it.

Signed-in readers only.