How do I compare DateTimes across different timezones in EK9?

← Date, Time, and Duration · Ref: Q548

EK9 automatically normalizes DateTimes to UTC for comparison. Two DateTimes representing the same instant are always equal, regardless of their timezone labels.

AUTOMATIC UTC NORMALIZATION

  utcNoon <- 2024-06-15T12:00:00Z
  nyMorning <- 2024-06-15T08:00:00-04:00
  require utcNoon == nyMorning

Both represent 12:00 UTC. The comparison sees through the timezone difference.

ALL COMPARISON OPERATORS NORMALIZE

This works for ==, <>, <, >, <=, >=, <=>:

  require 2024-06-15T12:00:00Z < 2024-06-15T09:00:00-04:00
  (12:00 UTC < 13:00 UTC)

SORTING WORKS CORRECTLY

A collection of DateTimes from different timezones sorts by their actual chronological order, not by clock reading.

DURATION SUBTRACTION ALSO NORMALIZES

  gap <- 2024-06-15T14:00:00-04:00 - 2024-06-15T12:00:00Z

The gap is the actual elapsed time between the two instants, accounting for timezones.

WHY THIS MATTERS

In JavaScript, comparing Date objects from different timezone strings is unreliable. In some databases, comparing TIMESTAMP WITHOUT TIME ZONE columns across regions gives wrong results. EK9 eliminates this entire category of bugs by always comparing the underlying UTC instant.

See Q541 for timezone basics. See Q542 for general date comparison. See Q545 for timezone conversion. See Q553 for common timezone mistakes.

Example

defines module qa.compare.across.timezones

  defines program
    CompareAcrossTimezonesDemo()
      stdout <- Stdout()

      // Same instant, different timezones
      utcNoon <- 2024-06-15T12:00:00Z
      nyMorning <- 2024-06-15T08:00:00-04:00
      tokyoEvening <- 2024-06-15T21:00:00+09:00

      // All represent 12:00 UTC
      require utcNoon == nyMorning
      require utcNoon == tokyoEvening
      require nyMorning == tokyoEvening
      stdout.println(`UTC noon == NY 8am: ${utcNoon == nyMorning}`)
      stdout.println(`UTC noon == Tokyo 9pm: ${utcNoon == tokyoEvening}`)

      // Different instants, different timezones
      laterUTC <- 2024-06-15T14:00:00Z
      require laterUTC > nyMorning
      require laterUTC > tokyoEvening
      stdout.println(`14:00 UTC > 08:00 NY: ${laterUTC > nyMorning}`)

      // Spaceship operator also normalizes
      ordering <- nyMorning <=> tokyoEvening
      require ordering == 0
      stdout.println(`NY <=> Tokyo: ${ordering}`)

      // Duration subtraction normalizes
      event1 <- 2024-06-15T10:00:00-04:00
      event2 <- 2024-06-15T16:00:00+02:00
      // event1 = 14:00 UTC, event2 = 14:00 UTC
      gap <- event2 - event1
      stdout.println(`Gap: ${gap}`)
      require event1 == event2

      // Collection of DateTimes from different timezones
      times as List of DateTime := [laterUTC, nyMorning, utcNoon]
      unsetDt <- DateTime()
      firstTime <- times.getOrDefault(0, unsetDt)
      lastIdx <- length times - 1
      lastTime <- times.getOrDefault(lastIdx, unsetDt)
      stdout.println(`First: ${firstTime}`)
      stdout.println(`Last: ${lastTime}`)

Common mistakes

E50060 — DateTime has no isEqual() method. EK9 uses the == operator directly, which automatically normalizes both sides to UTC before comparing. See ek9 -h E50060 for details.

Incorrect:

require utcNoon.isEqual(nyMorning)

Correct:

require utcNoon == nyMorning
Other ways to ask this
  • Do timezone differences affect DateTime comparison?
  • How does EK9 handle comparing UTC and local times?
  • Are DateTimes normalized before comparison?

Coming from another language?

Java: ZonedDateTime.isEqual() compares instants correctly, but == compares object identity (wrong). Python: timezone-aware datetimes compare correctly, but mixing naive and aware raises TypeError. JavaScript: Date comparison uses UTC internally but timezone string parsing is inconsistent. Go: time.Equal() normalizes, but == does not (compares location too). EK9: all comparison operators automatically normalize to UTC.

Keywords: migrate, instant, across, compare, duration, normalize, utc, equal, time, timezone, different