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.
public ZonedDateTime withZoneSameInstant(ZoneId zone)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$zone | ZoneId | The 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.UTCprints asZ;ZoneId.of("UTC")prints asZ[UTC]. PreferZoneOffset.UTC.- Converting by instant never hits a daylight-saving gap or overlap, because the instant is already fixed. Building a
ZonedDateTimefrom a local time can: a time in the spring gap is moved forward by the gap length.
Example
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.
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:00A 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.
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:00UTC 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.
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