How does DateTime work with timezones in EK9?

← Getting Started · Ref: Q92

DateTime combines date, time, and timezone into a single built-in type. It is the preferred type for timestamps, events, logging, and any data that needs timezone awareness.

LITERAL SYNTAX

UTC: 2024-06-15T10:30:00Z (the Z means UTC)
With offset: 2020-10-04T12:15:00-05:00 (five hours behind UTC)

CONSTRUCTOR FORMS

DateTime(2024, 06, 15, 10, 30) creates a DateTime with year, month, day, hour, minute. Additional constructors accept 3 args (date only), 4 args (with hour), or 6 args (with seconds).
DateTime() creates an unset DateTime (not now). DateTime().today() returns the current date and time. DateTime().now() also returns the current date and time.

ACCESSORS

All Date accessors: year(), month(), day(), dayOfMonth(), dayOfWeek(), dayOfYear()
Time accessors: hour(), minute(), second()
Timezone: zone() returns the timezone string (e.g. 'Z' or '-05:00'). offSetFromUTC() returns the offset as a Duration (e.g. PT-5H for the -05:00 timezone).
Extract parts: date() returns the Date portion, time() returns the Time portion.

ARITHMETIC

DateTime + Duration gives DateTime. DateTime - Duration gives DateTime. DateTime - DateTime gives Duration. Compound assignment with += and -= also works.

DATE PROMOTES TO DATETIME

The #^ promote operator converts Date to DateTime automatically. You can assign a Date to a DateTime variable directly.

Stream collection: cat durations | collect as DateTime (adds durations from epoch 1970-01-01T00:00:00Z).

See Q31 for Date and Time basics. See Q32 for Duration details. See Q44 for locale formatting with shortFormat(), longFormat(), and other locale-aware display methods.

Use 'ek9 -h DateTime' to see the full API.

See Q31 for date and time. See Q32 for duration. See Q541-Q553 for deep-dive timezone coverage including withSameInstant vs withZone, UTC storage, and cross-timezone comparison.

Example

defines module qa.datetime

  defines program
    DateTimeDemo()
      stdout <- Stdout()

      // DateTime literals with timezone
      meetingUTC <- 2024-06-15T10:30:00Z
      meetingEST <- 2020-10-04T12:15:00-05:00
      stdout.println(`UTC meeting: ${meetingUTC}`)
      stdout.println(`EST meeting: ${meetingEST}`)

      // Constructor form
      constructed <- DateTime(year: 2024, month: 06, dayOfMonth: 15, hour: 10, minute: 30)
      stdout.println(`Constructed matches UTC: ${constructed == meetingUTC}`)

      // DateTime() is unset, DateTime().today() gets current
      unsetDT <- DateTime()
      stdout.println(`Unset isSet: ${unsetDT?}`)
      currentDT <- DateTime().today()
      stdout.println(`Current: ${currentDT}`)

      // All accessors
      stdout.println(`Year: ${meetingUTC.year()}, Month: ${meetingUTC.month()}, Day: ${meetingUTC.day()}`)
      stdout.println(`Hour: ${meetingUTC.hour()}, Minute: ${meetingUTC.minute()}, Second: ${meetingUTC.second()}`)
      stdout.println(`Day of week: ${meetingUTC.dayOfWeek()}, Day of year: ${meetingUTC.dayOfYear()}`)

      // Timezone accessors
      tz <- meetingEST.zone()
      offset <- meetingEST.offSetFromUTC()
      stdout.println(`Timezone: ${tz}, Offset: ${offset}`)

      // Extract date and time parts
      datePart <- meetingUTC.date()
      timePart <- meetingUTC.time()
      stdout.println(`Date part: ${datePart}, Time part: ${timePart}`)

      // DateTime + Duration arithmetic
      meetingEnd <- meetingUTC + PT1H30M
      prepTime <- meetingUTC - PT30M
      stdout.println(`Meeting ends: ${meetingEnd}`)
      stdout.println(`Prep starts: ${prepTime}`)

      // DateTime - DateTime gives Duration
      laterMeeting <- 2024-06-15T14:00:00Z
      meetingGap <- laterMeeting - meetingUTC
      stdout.println(`Gap between meetings: ${meetingGap}`)

      // Compound assignment
      scheduled <- 2024-06-15T09:00:00Z
      scheduled += P1D
      stdout.println(`Rescheduled: ${scheduled}`)

      // Date promotes to DateTime
      birthday <- 1971-02-01
      birthdayDT as DateTime: birthday
      stdout.println(`Promoted: ${birthdayDT}`)

      // Stream collection from durations
      durations as List of Duration := [P1Y1M4D, PT2H30M]
      collectedDT <- cat durations | collect as DateTime
      stdout.println(`Collected: ${collectedDT}`)

      // Locale formatting
      enGB <- Locale("en_GB")
      stdout.println(`Short: ${enGB.shortFormat(meetingUTC)}`)
      stdout.println(`Long: ${enGB.longFormat(meetingUTC)}`)

Common mistakes

E50060 — EK9 DateTime uses short method names: zone(), date(), time() not getZone(), getDate(), getTime(). Triggers E50060 — method not resolved. See ek9 -h DateTime for the full API.

Incorrect:

meetingEST.getZone()

Correct:

meetingEST.zone()

E50060 — EK9 DateTime uses short method names: zone(), date(), time() — not Java-style getZone(), getDate(), getTime(). Using a Java-style name triggers E50060 — method not resolved. Use 'ek9 -h DateTime' to see the full API.

Incorrect:

tz <- meetingEST.getZone()

Correct:

tz <- meetingEST.zone()
Other ways to ask this
  • How do I use DateTime literals with UTC and offsets?
  • How does EK9 handle timezone conversion?
  • What is the difference between Date and DateTime in EK9?

Coming from another language?

Java: java.time.ZonedDateTime/OffsetDateTime (Java 8), verbose factory methods, no literals. Python: datetime.datetime with pytz/zoneinfo for timezones, no literals. Rust: no built-in, chrono crate with DateTime<Tz>. Go: time.Time with time.Location, no literals, bizarre reference format. JavaScript: Date has no timezone support beyond local/UTC, Temporal.ZonedDateTime still not finalised. C#: DateTimeOffset reasonable but no literals. EK9: DateTime built-in with literal syntax including timezone offset, zone()/offSetFromUTC() accessors, Duration arithmetic, Date promotion, no imports needed.

Keywords: first, beginner, intro, timestamp, start, combined, time, promote, zone, datetime, date, timezone, utc, offset