ZonedDateTime.withZoneSameInstant()

Show the same moment in another time zone, such as a UTC timestamp in a user's local time, and know how it differs from withZoneSameLocal.

Signature
public ZonedDateTime withZoneSameInstant(ZoneId zone)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$zoneZoneIdThe target zone, such as ZoneId.of("America/New_York") or ZoneOffset.UTC.

Return value

ZonedDateTimeA copy with the given zone and the same instant, so the local date and time change to match the new zone.

Errors

DateTimeException if the result exceeds the supported date range; NullPointerException if zone is null.

withZoneSameInstant(zone) answers "what time is it over there at this exact moment?". It keeps the instant and recalculates the local date and time for the new zone: 14:00 in London in July is 09:00 in New York and 22:00 in Tokyo. The result is a new ZonedDateTime; the original is unchanged.

Same instant versus same local

Its sibling, withZoneSameLocal(zone), does the opposite: it keeps 14:00 on the wall clock and changes the zone, which moves the moment by the offset difference. That is almost never what conversion code wants. If a timestamp shifts by exactly the offset after a conversion, check which of the two methods was called.

Converting for display

The back-end pattern is simple. Store and compute in UTC with Instant. When you need to show a time to a user, call instant.atZone(userZone), or if you already hold a ZonedDateTime, withZoneSameInstant(userZone), and then format it. The user's zone comes from their profile or the request, never from the server's default zone.

Use region IDs such as Europe/London, not fixed offsets such as +01:00. A region knows its daylight saving rules, so a 09:00 New York stand-up shows as 14:00 in London most of the year but 13:00 for the few weeks in March and the week around the end of October when the two countries have not yet both changed their clocks.

Comparing across zones

Two ZonedDateTime values for the same moment in different zones are not equals(), because equals also compares the zone. Use isEqual(), isBefore() and isAfter(), which compare instants, or compare toInstant() values. This matters for sets and map keys too.

Other notes

  • ZoneOffset.UTC prints as Z; ZoneId.of("UTC") prints as Z[UTC]. Prefer ZoneOffset.UTC.
  • Converting by instant never hits a daylight-saving gap or overlap, because the instant is already fixed. Building a ZonedDateTime from a local time can: a time in the spring gap is moved forward by the gap length.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Rendering an order's UTC created time in the customer's zone on an order history page.
  • Showing a meeting invitation's start time for every attendee in their own zone.
  • Normalising an incoming ZonedDateTime to UTC before storing or comparing it.

Frequently asked questions

How do I convert a time from one time zone to another in Java?

Call zonedDateTime.withZoneSameInstant(ZoneId.of("Target/Zone")). For an Instant, call instant.atZone(zone).

What is the difference between withZoneSameInstant and withZoneSameLocal?

withZoneSameInstant keeps the moment and changes the wall-clock time; withZoneSameLocal keeps the wall-clock time and changes the moment. Conversion almost always needs withZoneSameInstant.

Why are two ZonedDateTime values for the same moment not equal?

equals() also compares the zone. Use isEqual(), or compare their toInstant() values.

More examples

Showing a stored UTC timestamp in each user's zone

The order happened once, at one instant; each user sees it in their own zone, and for one of them it is already the next day.

Example · java
import java.time.*;
import java.time.format.*;

record User(String name, ZoneId zone) {}

void main() {
    Instant orderedAt = Instant.parse("2026-11-02T23:45:00Z");
    DateTimeFormatter f = DateTimeFormatter.ofPattern("EEE d MMM HH:mm xxx", Locale.ENGLISH);

    List<User> users = List.of(
        new User("ana", ZoneId.of("America/Sao_Paulo")),
        new User("ben", ZoneId.of("Europe/London")),
        new User("chen", ZoneId.of("Asia/Shanghai")));

    ZonedDateTime utc = orderedAt.atZone(ZoneOffset.UTC);
    for (User u : users) {
        IO.println(u.name() + ": " + utc.withZoneSameInstant(u.zone()).format(f));
    }
}
// Output:
// ana: Mon 2 Nov 20:45 -03:00
// ben: Mon 2 Nov 23:45 +00:00
// chen: Tue 3 Nov 07:45 +08:00

A New York stand-up seen from London

The US changes clocks on 8 March 2026 and the UK on 29 March, so for three weeks the gap is four hours instead of five. Region IDs handle this; fixed offsets would not.

Example · java
import java.time.*;

void main() {
    ZoneId newYork = ZoneId.of("America/New_York");
    ZoneId london = ZoneId.of("Europe/London");

    for (LocalDate day : List.of(LocalDate.of(2026, 3, 2), LocalDate.of(2026, 3, 16), LocalDate.of(2026, 4, 6))) {
        ZonedDateTime standup = ZonedDateTime.of(day, LocalTime.of(9, 0), newYork);
        IO.println(day + " London " + standup.withZoneSameInstant(london).toLocalTime());
    }
}
// Output:
// 2026-03-02 London 14:00
// 2026-03-16 London 13:00
// 2026-04-06 London 14:00

UTC flavours and the spring gap

ZoneOffset.UTC gives the plain Z form APIs expect. 01:30 on 29 March 2026 does not exist in London because clocks jump from 01:00 to 02:00, so building that local time moves it forward an hour; converting an instant never has this problem.

Example · java
import java.time.*;

void main() {
    ZonedDateTime t = ZonedDateTime.of(2026, 7, 15, 14, 0, 0, 0, ZoneId.of("Europe/London"));
    IO.println(t.withZoneSameInstant(ZoneOffset.UTC));
    IO.println(t.withZoneSameInstant(ZoneId.of("UTC")));

    ZonedDateTime inGap = ZonedDateTime.of(2026, 3, 29, 1, 30, 0, 0, ZoneId.of("Europe/London"));
    IO.println(inGap);
}
// Output:
// 2026-07-15T13:00Z
// 2026-07-15T13:00Z[UTC]
// 2026-03-29T02:30+01:00[Europe/London]

Discussion

  • Be the first to comment on this lesson.