How should I store dates in EK9 — UTC or local time?

← Date, Time, and Duration · Ref: Q547

Always store in UTC. Convert to local time only for display. This is the universal best practice and EK9 makes it natural.

WHY UTC FOR STORAGE

1. Unambiguous: UTC has no daylight saving transitions. 14:00 UTC is always 14:00 UTC.
2. Comparable: two UTC timestamps can be directly compared without timezone conversion.
3. Arithmetic: adding PT1H to a UTC time always moves forward exactly one hour. With local times, adding an hour across a DST boundary can give unexpected results.
4. Portable: UTC means the same thing everywhere. 'EST' could be US Eastern or Australian Eastern.

THE PATTERN

Store: always as UTC DateTime literals or UTC-constructed values.

  created <- 2024-06-15T14:00:00Z
  updated <- DateTime().now()

Display: convert to user's timezone with withSameInstant:

  userZone <- "America/New_York"
  displayTime <- stored.withSameInstant(userZone)

Format: use Locale for regional formatting:

  enUS <- Locale("en_US")
  formatted <- enUS.longFormat(displayTime)

FULL PIPELINE

  stored <- 2024-06-15T14:00:00Z
  local <- stored.withSameInstant("Europe/Berlin")
  deDE <- Locale("de_DE")
  stdout.println(deDE.longFormat(local))

Store (UTC) to display (timezone) to format (locale) is the three-step pipeline.

See Q541 for timezone basics. See Q545 for timezone conversion. See Q546 for withSameInstant vs withZone. See Q549 for locale formatting. See Q553 for common timezone mistakes.

Example

#!ek9
defines module qa.store.utc.display.local

  defines program
    StoreUtcDisplayLocalDemo()
      stdout <- Stdout()

      // STORE: always in UTC
      orderCreated <- 2024-06-15T14:30:00Z
      orderShipped <- 2024-06-16T09:15:00Z
      stdout.println(`Stored (UTC): ${orderCreated}`)

      // COMPARE: UTC timestamps compare correctly
      require orderShipped > orderCreated
      elapsed <- orderShipped - orderCreated
      stdout.println(`Time to ship: ${elapsed}`)

      // DISPLAY: convert to user's timezone
      userZone <- "America/New_York"
      localCreated <- orderCreated.withSameInstant(userZone)
      localShipped <- orderShipped.withSameInstant(userZone)
      stdout.println(`User sees created: ${localCreated}`)
      stdout.println(`User sees shipped: ${localShipped}`)

      // FORMAT: locale-aware display
      enUS <- Locale("en_US")
      stdout.println(`Formatted: ${enUS.longFormat(localCreated)}`)

      // Full pipeline: store -> timezone -> locale
      deDE <- Locale("de_DE")
      berlinTime <- orderCreated.withSameInstant("Europe/Berlin")
      stdout.println(`German user sees: ${deDE.longFormat(berlinTime)}`)

      // Multiple users see different times for the same event
      tokyoTime <- orderCreated.withSameInstant("Asia/Tokyo")
      jaJP <- Locale("ja_JP")
      stdout.println(`Tokyo user sees: ${jaJP.longFormat(tokyoTime)}`)

      // All stored values stay UTC for correct comparison
      require localCreated == orderCreated
      require berlinTime == orderCreated
      require tokyoTime == orderCreated

Common mistakes

E50060 — DateTime has no toLocalTime() method. Use withSameInstant() to convert UTC timestamps to a local timezone for display. See ek9 -h E50060 for details.

Incorrect:

localCreated <- orderCreated.toLocalTime(userZone)

Correct:

localCreated <- orderCreated.withSameInstant(userZone)
Other ways to ask this
  • Should I store timestamps in UTC?
  • What is the best practice for storing dates?
  • How do I handle the store UTC display local pattern?

Coming from another language?

All languages: the 'store UTC, display local' pattern is universal best practice. Java: Instant for storage, ZonedDateTime for display. Python: datetime.utcnow() (deprecated) or datetime.now(UTC). JavaScript: Date internally stores UTC, toLocaleString() for display. Go: time.UTC for storage, In(loc) for display. EK9: DateTime literal with Z suffix for storage, withSameInstant() for display, Locale for formatting.

Keywords: best practice, display, duration, local, locale, pattern, store, time, timezone, utc