How to Compare ZonedDateTime Across Different Timezones

To compare two Temporal.ZonedDateTime values across zones, decide first whether you mean "same exact moment" (use Temporal.ZonedDateTime.compare() or .equals(), which work on the UTC instant) or "same local clock time" (convert with .toPlainTime() and use Temporal.PlainTime.compare()). This page is part of Working with ZonedDateTime Objects.

Why this scenario is tricky

The failure mode is conflating two different questions that look identical in code. New York at 12:00 and London at 17:00 on the same date can be the exact same instant, yet New York at 09:00 and Los Angeles at 09:00 are the same wall-clock time but three hours apart in reality. If you reach for the wrong comparison — or worse, compare the ISO strings directly — you get false negatives whenever the offset suffixes differ, even for identical instants.

DST makes it sharper still. The local string 2024-11-03T01:30:00 maps to two distinct instants in New York because the fall-back hour repeats. Compare such strings textually and they look equal; compared as instants they are an hour apart. The fix is to always resolve a bare local time to a concrete ZonedDateTime before comparing, and to pick a comparison axis on purpose.

The two axes are shown below.

Instant comparison versus wall-clock comparisonInstant comparison maps both ZonedDateTimes onto a shared UTC axis; equal instants are equal. Wall-clock comparison strips the zone and compares only the local HH:MM:SS.Instant comparisonNY 12:00 -04London 17:00 +01shared UTC axisboth = 16:00 UTC.equals() → truecompare epoch instantsWall-clock comparisonLA 09:00 -07NY 09:00 -04zone stripped → PlainTime09:00:0009:00:00PlainTime.compare → 0but 3h apart in UTC

Minimal working solution

import { Temporal } from '@js-temporal/polyfill';

const ny = Temporal.ZonedDateTime.from('2024-11-01T12:00:00-04:00[America/New_York]');
const london = Temporal.ZonedDateTime.from('2024-11-01T17:00:00+01:00[Europe/London]');

// Instant axis: compares the underlying UTC moment, ignoring offsets.
console.log(Temporal.ZonedDateTime.compare(ny, london)); // 0 — same instant
console.log(ny.equals(london));                          // true

// Wall-clock axis: strip the zone, compare only HH:MM:SS.
console.log(Temporal.PlainTime.compare(ny.toPlainTime(), london.toPlainTime())); // -1

Full production version

import { Temporal } from '@js-temporal/polyfill';

export type ComparisonMode = 'instant' | 'wall-clock';
export type Ordering = -1 | 0 | 1;

function coerce(input: Temporal.ZonedDateTime | string): Temporal.ZonedDateTime {
  if (input instanceof Temporal.ZonedDateTime) return input;
  try {
    // Requires a bracketed IANA zone; a bare local string throws here on purpose.
    return Temporal.ZonedDateTime.from(input);
  } catch {
    throw new Error(`Invalid ZonedDateTime input: "${input}"`);
  }
}

export function compareZoned(
  a: Temporal.ZonedDateTime | string,
  b: Temporal.ZonedDateTime | string,
  mode: ComparisonMode = 'instant'
): Ordering {
  const za = coerce(a);
  const zb = coerce(b);

  if (mode === 'instant') {
    // compare() returns -1 | 0 | 1 from the epoch instants — DST-safe.
    return Temporal.ZonedDateTime.compare(za, zb) as Ordering;
  }
  // Wall-clock: discard zone and instant, keep only local time of day.
  return Temporal.PlainTime.compare(za.toPlainTime(), zb.toPlainTime()) as Ordering;
}

When comparison feeds into locale-aware ordering for display lists, hand the result to the patterns in locale-sensitive date comparison and sorting rather than sorting formatted strings.

Verification snippet

import { Temporal } from '@js-temporal/polyfill';

// Same instant in two zones must be equal on the instant axis...
console.assert(
  compareZoned(
    '2024-11-01T12:00:00-04:00[America/New_York]',
    '2024-11-01T17:00:00+01:00[Europe/London]'
  ) === 0,
  'identical instants should compare equal'
);

// ...and the two fall-back occurrences must NOT be equal.
const local = Temporal.PlainDateTime.from('2024-11-03T01:30:00');
const earlier = local.toZonedDateTime('America/New_York', { disambiguation: 'earlier' });
const later = local.toZonedDateTime('America/New_York', { disambiguation: 'later' });
console.assert(!earlier.equals(later), 'fall-back occurrences are one hour apart');

Common pitfalls

Frequently Asked Questions

Does Temporal.ZonedDateTime.equals() account for different timezones?

Yes. It compares the underlying UTC epoch nanoseconds, not the local representation. Two instances in different IANA zones are equal when they point at the exact same moment, regardless of their offsets.

How do I compare only the local clock time across zones?

Convert both values with .toPlainTime(), then use Temporal.PlainTime.compare() or .equals(). This strips the zone and instant and compares only the HH:MM:SS.sss components, which is what you want for business-hours rules.

What happens during DST fall-back when comparing times?

Resolve the ambiguous local time with Temporal.PlainDateTime.from(input).toZonedDateTime(zone, { disambiguation: 'earlier' | 'later' }) before comparing. The two occurrences are one hour apart, so they are not .equals(); passing the bare string to Temporal.ZonedDateTime.from() without an offset throws.