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

What is the best practice for storing dates?

How do I handle the store UTC display local pattern?

Answer:
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.

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

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.

best practice
display
duration
local
locale
pattern
store
time
timezone
utc

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


Question: How do I compare DateTimes across different timezones in EK9?

Do timezone differences affect DateTime comparison?

How does EK9 handle comparing UTC and local times?

Are DateTimes normalized before comparison?

Answer:
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.

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}`)

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.

across compare different duration equal instant migrate normalize time timezone utc

Explanation: 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.


Question: How do I format dates for different locales in EK9?

How do I display dates in German or French format?

How do locale formatting levels work with dates?

Answer:
EK9's Locale type provides four named formatting levels that automatically adapt to regional conventions. No format pattern strings needed.

FOUR FORMATTING LEVELS FOR DATES
  shortFormat: compact numeric (03/10/2020 GB, 10/3/20 US)
  mediumFormat: abbreviated month (Oct 3, 2020 US, 3 Oct 2020 GB)
  longFormat: full month name (3. Oktober 2020 DE)
  fullFormat: includes day-of-week (Samstag, 3. Oktober 2020 DE)

Each locale adjusts automatically: date order (day/month vs month/day), separators, month names, and day names.

SAME METHODS FOR TIME AND DATETIME
The four formatting levels also work on Time and DateTime:
  locale.shortFormat(09:30:00) gives locale-appropriate time
  locale.longFormat(2024-06-15T10:30:00Z) gives full date+time+timezone

DAY OF WEEK
  locale.dayOfWeek(date) returns the localized day name:
  Locale('de_DE').dayOfWeek(2024-06-15) gives 'Samstag'
  Locale('en_GB').dayOfWeek(2024-06-15) gives 'Saturday'

WHY NO PATTERN STRINGS
Pattern strings like 'yyyy-MM-dd' or 'dd/MM/yyyy' are error-prone: 'mm' means minutes in Java but months in Excel. Named levels are unambiguous and locale-correct by default.

See Q44 for the complete Locale formatting guide. See Q537 for ISO 8601 formatting. See Q551 for RFC 7231 HTTP date headers.

defines module qa.locale.date.formatting

  defines program
    LocaleDateFormattingDemo()
      stdout <- Stdout()

      wedding <- 2020-10-03
      lunchTime <- 12:00:01
      meeting <- 2024-06-15T10:30:00Z

      // Create locales
      enGB <- Locale("en_GB")
      enUS <- Locale("en_US")
      deutsch <- Locale("de_DE")
      francais <- Locale("fr_FR")

      // Date formatting - four levels
      stdout.println("=== Date Formatting ===")
      stdout.println(`GB short: ${enGB.shortFormat(wedding)}`)
      stdout.println(`US short: ${enUS.shortFormat(wedding)}`)
      stdout.println(`GB medium: ${enGB.mediumFormat(wedding)}`)
      stdout.println(`US medium: ${enUS.mediumFormat(wedding)}`)
      stdout.println(`DE long: ${deutsch.longFormat(wedding)}`)
      stdout.println(`FR long: ${francais.longFormat(wedding)}`)
      stdout.println(`GB full: ${enGB.fullFormat(wedding)}`)
      stdout.println(`DE full: ${deutsch.fullFormat(wedding)}`)

      // Time formatting
      stdout.println("=== Time Formatting ===")
      stdout.println(`GB short time: ${enGB.shortFormat(lunchTime)}`)
      stdout.println(`US short time: ${enUS.shortFormat(lunchTime)}`)
      stdout.println(`DE medium time: ${deutsch.mediumFormat(lunchTime)}`)

      // DateTime formatting
      stdout.println("=== DateTime Formatting ===")
      stdout.println(`US short DT: ${enUS.shortFormat(meeting)}`)
      stdout.println(`GB medium DT: ${enGB.mediumFormat(meeting)}`)
      stdout.println(`DE long DT: ${deutsch.longFormat(meeting)}`)
      stdout.println(`FR full DT: ${francais.fullFormat(meeting)}`)

      // Day of week
      stdout.println("=== Day of Week ===")
      stdout.println(`GB: ${enGB.dayOfWeek(wedding)}`)
      stdout.println(`DE: ${deutsch.dayOfWeek(wedding)}`)
      stdout.println(`FR: ${francais.dayOfWeek(wedding)}`)

Java: DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(). Python: babel.dates.format_date(style='short', locale='de_DE'). JavaScript: Intl.DateTimeFormat with options. Go: no built-in locale formatting. Rust: no built-in, chrono-locale crate. EK9: Locale('de_DE').shortFormat(date) four named levels, no pattern strings.

date dayOfWeek duration format fullFormat i18n internationalization locale longFormat mediumFormat migrate regional shortFormat time timezone

stdout.println(`GB short: ${enGB.format(wedding, "short")}`)

Explanation: Locale has no generic format() method. Use the four named methods: shortFormat(), mediumFormat(), longFormat(), fullFormat(). See ek9 -h E50060 for details.


Question: How do I handle calendar edge cases like month-end and leap years in EK9?

What happens when adding a month to January 31st?

Why is month arithmetic non-associative in other languages?

Answer:
EK9 uses a 30-day month approximation for Duration arithmetic. This makes month arithmetic predictable and associative, at the cost of exact calendar precision.

THE DURATION SUMMING PROBLEM (Deep Dive)
In Java and Python, month arithmetic uses true calendar months:
  Java: Jan 31 + 1 month = Feb 28 (clamped)
        Feb 28 + 1 month = Mar 28
  But:  Jan 31 + 2 months = Mar 31 (different!)

This happens because each addition clamps to the month's length, and the clamping at each intermediate step loses information. Month arithmetic is NOT associative: (a + b) + c is not equal to a + (b + c). This creates subtle bugs when accumulating monthly intervals.

EK9'S APPROACH
P1M = exactly 30 days (30 x 86400 seconds). Always.
P1Y = 360 days (12 x 30). Always.

This means:
  date + P1M + P1M = date + 60 days
  date + P2M = date + 60 days (same!)
Month arithmetic IS associative. P1M * 2 == P2M. Duration addition is commutative and associative. No surprises from intermediate clamping.

THE TRADE-OFF
EK9 durations are predictable but approximate:
  Feb 1 + P1M = Mar 3 (30 days from Feb 1), not Feb 28
  Jan 31 + P1M = Mar 2 (30 days from Jan 31), not Feb 28

For calendar-precise month logic, construct new dates from components:
  currentMonth <- date.month()
  nextMonthDate <- Date(date.year(), currentMonth + 1, date.day())
This uses the calendar's own month boundaries.

LEAP YEARS
Date handles leap years correctly in its calendar:
  Date(2024, 02, 29) is valid (2024 is a leap year)
  Date(2023, 02, 29) is invalid (produces unset)
But Duration arithmetic with P1Y adds 360 days, not 365 or 366.

DESIGN RATIONALE
Most duration use cases (scheduling, timeouts, intervals) need predictability more than calendar precision. 'Add 2 months' meaning '60 days' is always correct for billing cycles, subscription periods, and timeout calculations. True calendar month arithmetic is only needed for UI features like 'same day next month'.

See Q32 for Duration basics. See Q540 for the duration summing pitfall introduction. See Q539 for date subtraction. See Q556 for Time wrapping.

defines module qa.calendar.edge.cases

  defines program
    CalendarEdgeCasesDemo()
      stdout <- Stdout()

      // Leap year handling
      leapDate <- Date(2024, 02, 29)
      stdout.println(`Leap day 2024: ${leapDate}`)
      require leapDate?

      // Non-leap year: Feb 29 is invalid -> unset
      noLeap <- Date(2023, 02, 29)
      require ~noLeap?
      stdout.println(`Feb 29 2023 isSet: ${noLeap?}`)

      // P1M = 30 days, always
      jan31 <- 2024-01-31
      plusOneMonth <- jan31 + P1M
      plusTwoMonths <- jan31 + P2M
      plusOneOne <- jan31 + P1M + P1M
      stdout.println(`Jan 31 + P1M: ${plusOneMonth}`)
      stdout.println(`Jan 31 + P2M: ${plusTwoMonths}`)
      stdout.println(`Jan 31 + P1M + P1M: ${plusOneOne}`)

      // Associative: P1M + P1M == P2M
      require plusTwoMonths == plusOneOne
      stdout.println(`P2M == P1M + P1M: ${plusTwoMonths == plusOneOne}`)

      // Duration math is predictable
      oneMonth <- P1M
      twoMonths <- P2M
      require oneMonth + oneMonth == twoMonths
      require oneMonth * 2 == twoMonths
      stdout.println(`P1M + P1M == P2M: ${oneMonth + oneMonth == twoMonths}`)

      // P1Y = 360 days
      oneYear <- P1Y
      twelveMonths <- P1M * 12
      require oneYear == twelveMonths
      stdout.println(`P1Y == P1M * 12: ${oneYear == twelveMonths}`)

      // Calendar-precise month logic: construct from components
      baseDate <- 2024-01-31
      nextMonth <- Date(baseDate.year(), baseDate.month() + 1, 28)
      stdout.println(`Calendar next month end: ${nextMonth}`)

      // Month-end dates
      march31 <- 2024-03-31
      plus30days <- march31 + P1M
      stdout.println(`Mar 31 + P1M (30 days): ${plus30days}`)

Java: YearMonth, LocalDate.plusMonths() handles month-end clamping (Jan 31 + 1M = Feb 28) but is non-associative. Period.ofMonths(1) stored as months, applied at calculation time. Python: relativedelta(months=+1) same clamping behavior. Go: AddDate(0,1,0) normalizes (Jan 31 + 1M = Mar 2 or 3 depending on Feb). EK9: P1M = 30 days always, associative, predictable. Calendar-precise month logic via component construction.

30 days
associative
calendar
clamping
duration
edge case
leap year
migrate
month-end
predictable
summing
time
timezone

require oneMonth.plus(oneMonth) == twoMonths

Explanation: Duration has no plus() method. EK9 uses the + operator for duration arithmetic. See ek9 -h E50060 for details.


Question: How do I generate RFC 7231 HTTP date headers in EK9?

How do I create an HTTP Date header value?

Answer:
EK9 provides a built-in rfc7231() method on DateTime that produces the exact format required by HTTP headers.

ONE-LINER
  httpDate <- DateTime().now().rfc7231()
Produces: 'Sat, 15 Jun 2024 14:30:00 GMT'

THE RFC 7231 FORMAT
HTTP/1.1 requires dates in the format: 'Day, DD Mon YYYY HH:MM:SS GMT'
Examples:
  'Sat, 15 Jun 2024 14:30:00 GMT'
  'Wed, 25 Dec 2024 00:00:00 GMT'
The value is always in GMT (UTC). The rfc7231() method automatically converts to UTC regardless of the DateTime's timezone.

WHERE HTTP DATES ARE USED
  Date: response header (when the response was generated)
  Last-Modified: when the resource was last changed
  Expires: when the cached response becomes stale
  If-Modified-Since: conditional request header
  Retry-After: when to retry a failed request

WHY BUILT-IN
Every HTTP-serving application needs this format. In Java, you need DateTimeFormatter.RFC_1123_DATE_TIME. In Go, you use time.RFC1123. Making it a one-liner on DateTime eliminates a common boilerplate pattern.

See Q537 for general date formatting. See Q549 for locale formatting. See Q541 for timezone handling.

defines module qa.rfc7231.http.headers

  defines program
    Rfc7231HttpHeadersDemo()
      stdout <- Stdout()

      // Generate RFC 7231 HTTP date from current time
      now <- DateTime().now()
      httpDate <- now.rfc7231()
      stdout.println(`Date: ${httpDate}`)

      // From a specific DateTime
      event <- 2024-06-15T14:30:00Z
      stdout.println(`Event: ${event.rfc7231()}`)

      // Works from any timezone - always converts to GMT
      nyEvent <- 2024-12-25T00:00:00-05:00
      stdout.println(`NY event: ${nyEvent.rfc7231()}`)

      // Typical HTTP response headers
      stdout.println(`Date: ${DateTime().now().rfc7231()}`)
      stdout.println(`Last-Modified: ${event.rfc7231()}`)

      // The result is a String
      headerValue <- event.rfc7231()
      require headerValue?
      stdout.println(`Type check: ${headerValue}`)

Java: DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)). Python: email.utils.formatdate(usegmt=True). JavaScript: new Date().toUTCString(). Go: t.UTC().Format(time.RFC1123). Rust: chrono format with custom pattern. EK9: dateTime.rfc7231() one-liner, always GMT.

api date duration expires format gmt header http last-modified migrate rfc7231 time timezone web

Explanation: DateTime has no toRFC1123() method. EK9 uses rfc7231() which produces the HTTP-standard date format. See ek9 -h E50060 for details.


Question: How does Date promote to DateTime in EK9?

What does the #^ promote operator do with Date?

How do I extract Date and Time from DateTime?

Answer:
Date automatically promotes to DateTime via the #^ operator. DateTime can also be decomposed back into its Date and Time parts.

DATE TO DATETIME PROMOTION
The #^ promote operator converts Date to DateTime (midnight UTC):
  birthday <- 1971-02-01
  birthdayDT <- #^ birthday
  birthdayDT is 1971-02-01T00:00:00Z

Automatic promotion also occurs when assigning Date to a DateTime variable:
  dtValue as DateTime: birthday
  dtValue is 1971-02-01T00:00:00Z

Or via the DateTime(Date) constructor:
  explicit <- DateTime(birthday)

DATETIME TO DATE AND TIME EXTRACTION
The date() method extracts the Date portion:
  myDate <- myDateTime.date()

The time() method extracts the Time portion:
  myTime <- myDateTime.time()

Alternatively, use extraction operators:
  #< extracts the Date (lower/coarser part)
  #> extracts the Time (upper/finer part)
  datePart <- #< myDateTime
  timePart <- #> myDateTime

MILLISECOND TO DURATION
Similar promotion: Millisecond promotes to Duration via #^:
  ms <- 5000ms
  dur <- #^ ms
  dur is PT5S

See Q25 for the promote operator in general. See Q31 for Date and Time basics. See Q92 for DateTime with timezones. See Q554 for extracting DateTime components.

defines module qa.date.promote.datetime

  defines program
    DatePromoteDateTimeDemo()
      stdout <- Stdout()

      birthday <- 1971-02-01

      // Explicit promote operator
      promoted <- #^ birthday
      stdout.println(`Promoted: ${promoted}`)

      // Assignment promotion
      dtValue as DateTime: birthday
      stdout.println(`Assigned: ${dtValue}`)

      // Constructor promotion
      explicit <- DateTime(birthday)
      stdout.println(`Constructor: ${explicit}`)

      // All three are equivalent
      require promoted == dtValue
      require dtValue == explicit

      // DateTime decomposition
      meeting <- 2024-06-15T10:30:00Z

      // Method extraction
      datePart <- meeting.date()
      timePart <- meeting.time()
      stdout.println(`Date part: ${datePart}`)
      stdout.println(`Time part: ${timePart}`)

      // Operator extraction
      dateExtract <- #< meeting
      timeExtract <- #> meeting
      stdout.println(`#< extract: ${dateExtract}`)
      stdout.println(`#> extract: ${timeExtract}`)

      require datePart == dateExtract
      require timePart == timeExtract

      // Millisecond to Duration promotion
      ms <- 5000ms
      dur <- #^ ms
      stdout.println(`5000ms promoted: ${dur}`)

      // Round-trip: Date -> DateTime -> Date
      original <- 2024-03-15
      asDT as DateTime: original
      backToDate <- asDT.date()
      require backToDate == original
      stdout.println(`Round-trip: ${original} -> ${asDT} -> ${backToDate}`)

Java: LocalDate.atStartOfDay(ZoneOffset.UTC) to convert Date to DateTime. LocalDateTime.toLocalDate() and toLocalTime() for extraction. Python: datetime.combine(date, time.min) for promotion. Go: no separate Date type. Rust: NaiveDate with_hms() methods. EK9: #^ promotes Date to DateTime automatically, date() and time() extract parts, #< and #> extraction operators.

constructor convert date date() datetime decompose duration extract promote time time() timezone

datePart <- meeting.toDate()
      timePart <- meeting.toTime()

Explanation: DateTime has no toDate() or toTime() methods. Use date() and time() to extract the Date and Time components. See ek9 -h E50060 for details.


Question: What are common timezone mistakes and how does EK9 prevent them?

How does EK9 handle daylight saving time?

What are the most common timezone errors in programming?

Answer:
Six common timezone mistakes and how EK9 addresses each one.

1. STORING LOCAL TIME WITHOUT TIMEZONE
Mistake: saving '2024-06-15 10:30' without specifying which timezone.
Java: LocalDateTime has no timezone, stored values are ambiguous.
EK9: DateTime always carries a timezone. You cannot create a DateTime without one. Literals require Z or an offset.

2. COMPARING NAIVE AND AWARE DATETIMES
Mistake: comparing a timezone-unaware timestamp with a timezone-aware one.
Python: raises TypeError when mixing naive and aware datetimes.
EK9: all DateTimes are timezone-aware. Comparison automatically normalizes to UTC (see Q548).

3. CONFUSING withSameInstant AND withZone
Mistake: using the wrong conversion method, creating a DateTime that represents a different moment than intended.
Java: withZoneSameInstant() vs withZoneSameLocal() have confusing names.
EK9: withSameInstant() and withZone() have clear, descriptive names (see Q546).

4. ASSUMING FIXED UTC OFFSETS
Mistake: hardcoding UTC-5 for New York (fails during daylight saving time).
EK9: use IANA names like 'America/New_York' which automatically handle DST transitions.

5. IGNORING DST IN ARITHMETIC
Mistake: adding 24 hours and expecting to land on the same time tomorrow (fails on DST transition days).
EK9: Duration arithmetic on DateTime handles DST correctly. Adding P1D adds one calendar day regardless of DST.

6. USING AMBIGUOUS TIMEZONE ABBREVIATIONS
Mistake: using 'EST' (could be US Eastern or Australian Eastern).
EK9: IANA names are unambiguous. 'America/New_York' and 'Australia/Sydney' are distinct.

See Q541 for timezone basics. See Q545 for timezone conversion. See Q546 for withSameInstant vs withZone. See Q547 for UTC storage. See Q548 for cross-timezone comparison.

defines module qa.timezone.mistakes

  defines constant
    newYorkZone <- "America/New_York"

  defines program
    TimezoneMistakesDemo()
      stdout <- Stdout()

      // MISTAKE 1: EK9 DateTimes always have a timezone
      // You cannot create an ambiguous timestamp
      utcEvent <- 2024-06-15T10:30:00Z
      offsetEvent <- 2024-06-15T10:30:00-04:00
      stdout.println(`UTC: ${utcEvent}, zone: ${utcEvent.zone()}`)
      stdout.println(`Offset: ${offsetEvent}, zone: ${offsetEvent.zone()}`)

      // MISTAKE 2: All comparisons normalize to UTC
      // No mixing naive and aware - everything is aware
      require utcEvent <> offsetEvent
      stdout.println(`Different instants: ${utcEvent <> offsetEvent}`)

      // MISTAKE 3: Clear method names
      // withSameInstant = same moment, different display
      nyDisplay <- utcEvent.withSameInstant(newYorkZone)
      stdout.println(`Same instant NY: ${nyDisplay}`)
      require nyDisplay == utcEvent

      // withZone = same clock, different moment
      nyLabel <- utcEvent.withZone(newYorkZone)
      stdout.println(`Same clock NY: ${nyLabel}`)
      require nyLabel <> utcEvent

      // MISTAKE 4: Use IANA names, not fixed offsets
      // newYorkZone handles DST automatically
      summer <- 2024-06-15T12:00:00Z
      winter <- 2024-12-15T12:00:00Z
      nySummer <- summer.withSameInstant(newYorkZone)
      nyWinter <- winter.withSameInstant(newYorkZone)
      stdout.println(`NY summer offset: ${nySummer.zone()}`)
      stdout.println(`NY winter offset: ${nyWinter.zone()}`)

      // MISTAKE 5: Duration arithmetic is calendar-safe
      beforeDST <- 2024-03-09T12:00:00-05:00
      plusOneDay <- beforeDST + P1D
      stdout.println(`Before DST: ${beforeDST}`)
      stdout.println(`+ P1D: ${plusOneDay}`)

      // MISTAKE 6: IANA names are unambiguous
      usEast <- utcEvent.withSameInstant(newYorkZone)
      auEast <- utcEvent.withSameInstant("Australia/Sydney")
      stdout.println(`US Eastern: ${usEast}`)
      stdout.println(`AU Eastern: ${auEast}`)

All languages have timezone pitfalls. Java: LocalDateTime loses timezone, ZonedDateTime methods have confusing names. Python: naive vs aware datetimes, pytz normalize() gotcha. JavaScript: Date has no timezone awareness beyond local/UTC. Go: time.Location is opaque, DST handled internally. EK9: all DateTimes carry timezone, IANA names, clear method names, UTC-normalized comparison.

aware
bug
daylight saving
dst
duration
iana
migrate
mistake
naive
offset
prevention
time
timezone

Explanation: DateTime has no withZoneSameInstant() method (that is the Java naming). EK9 uses the clearer name withSameInstant(). See ek9 -h E50060 for details.


Question: How do I extract date and time components from a DateTime in EK9?

How do I get the year, month, and day from a DateTime?

How do I access individual parts of a DateTime?

What accessor methods does DateTime have?

Answer:
DateTime provides accessor methods for all date and time components, plus decomposition methods for Date and Time parts.

DATE COMPONENT ACCESSORS
  dt.year() returns the year (e.g., 2024)
  dt.month() returns the month (1-12)
  dt.day() returns the day of month (1-31)
  dt.dayOfMonth() same as day()
  dt.dayOfWeek() returns day of week (1=Monday through 7=Sunday)
  dt.dayOfYear() returns day of year (1-366)

TIME COMPONENT ACCESSORS
  dt.hour() returns the hour (0-23)
  dt.minute() returns the minute (0-59)
  dt.second() returns the second (0-59)

TIMEZONE ACCESSORS
  dt.zone() returns the timezone string ('Z', '-05:00', etc.)
  dt.offSetFromUTC() returns the UTC offset as Duration

DECOMPOSITION METHODS
  dt.date() extracts the Date portion
  dt.time() extracts the Time portion

EXTRACTION OPERATORS
  #< dt extracts Date (the lower/coarser component)
  #> dt extracts Time (the upper/finer component)

DATE AND TIME ACCESSORS
Date has: year(), month(), day(), dayOfMonth(), dayOfWeek(), dayOfYear()
Time has: hour(), minute(), second()
All accessors return Integer values that are set when the source is set, and unset when the source is unset.

See Q31 for Date and Time basics. See Q92 for DateTime. See Q552 for Date-to-DateTime promotion. See Q555 for constructing from components.

defines module qa.extract.components

  defines program
    ExtractComponentsDemo()
      stdout <- Stdout()

      meeting <- 2024-06-15T10:30:45Z

      // Date components
      stdout.println(`Year: ${meeting.year()}`)
      stdout.println(`Month: ${meeting.month()}`)
      stdout.println(`Day: ${meeting.day()}`)
      stdout.println(`Day of month: ${meeting.dayOfMonth()}`)
      stdout.println(`Day of week: ${meeting.dayOfWeek()}`)
      stdout.println(`Day of year: ${meeting.dayOfYear()}`)

      // Time components
      stdout.println(`Hour: ${meeting.hour()}`)
      stdout.println(`Minute: ${meeting.minute()}`)
      stdout.println(`Second: ${meeting.second()}`)

      // Timezone components
      stdout.println(`Zone: ${meeting.zone()}`)
      stdout.println(`UTC offset: ${meeting.offSetFromUTC()}`)

      // Decomposition methods
      datePart <- meeting.date()
      timePart <- meeting.time()
      stdout.println(`Date part: ${datePart}`)
      stdout.println(`Time part: ${timePart}`)

      // Extraction operators
      dateExtract <- #< meeting
      timeExtract <- #> meeting
      require datePart == dateExtract
      require timePart == timeExtract

      // Date accessors
      birthday <- 1971-02-01
      stdout.println(`Birthday year: ${birthday.year()}`)
      stdout.println(`Birthday month: ${birthday.month()}`)
      stdout.println(`Birthday day: ${birthday.day()}`)

      // Time accessors
      alarm <- 07:30:00
      stdout.println(`Alarm hour: ${alarm.hour()}`)
      stdout.println(`Alarm minute: ${alarm.minute()}`)
      stdout.println(`Alarm second: ${alarm.second()}`)

      // Unset propagation
      unsetDT <- DateTime()
      stdout.println(`Unset year isSet: ${unsetDT.year()?}`)
      stdout.println(`Unset hour isSet: ${unsetDT.hour()?}`)

Java: getYear(), getMonthValue(), getDayOfMonth(), etc. Python: .year, .month, .day attributes. JavaScript: getFullYear(), getMonth() (0-indexed!), getDate(). Go: t.Year(), t.Month(), t.Day(). Rust: chrono year(), month(), day(). EK9: year(), month(), day(), hour(), minute(), second() methods, all 1-indexed, plus date()/time() decomposition.

accessor component date datetime day decompose duration extract hour minute month second time timezone year zone

Explanation: DateTime has no getYear() method (that is the Java naming). EK9 uses year() without the 'get' prefix. See ek9 -h E50060 for details.


Question: How do I construct a DateTime from individual components in EK9?

What constructor forms does DateTime have?

How do I create a Date from year, month, and day?

How do I build a DateTime programmatically?

Answer:
Date, Time, and DateTime all support component-based construction.

DATE CONSTRUCTORS
  Date(year, month, dayOfMonth) from three integers
  Date(daysFromEpoch) from epoch day count
  Date(stringValue) from ISO 8601 string
  Date() unset date

TIME CONSTRUCTORS
  Time(hour, minute) two components
  Time(hour, minute, second) three components
  Time(secondOfDay) from seconds since midnight
  Time(stringValue) from ISO 8601 string
  Time() unset time

DATETIME CONSTRUCTORS
  DateTime(year, month, dayOfMonth) date only (midnight UTC)
  DateTime(year, month, dayOfMonth, hour) with hour
  DateTime(year, month, dayOfMonth, hour, minute) with minute
  DateTime(year, month, dayOfMonth, hour, minute, second) full
  DateTime(dateValue) from a Date (promotes)
  DateTime(stringValue) from ISO 8601 string
  DateTime() unset datetime

LITERAL SYNTAX
For known values, literals are more readable:
  date <- 2024-06-15
  time <- 10:30:00
  dt <- 2024-06-15T10:30:00Z

COMPONENT CONSTRUCTION FOR DYNAMIC VALUES
When values come from variables or calculations:
  year <- 2024
  month <- 6
  dayVal <- 15
  computed <- Date(year, month, dayVal)

INVALID COMPONENTS PRODUCE UNSET
  invalid <- Date(2024, 13, 01) produces an unset Date (month 13 does not exist).
  invalid <- Time(25, 00) produces an unset Time.

See Q538 for string parsing. See Q552 for Date-to-DateTime promotion. See Q554 for extracting components.

defines module qa.construct.from.components

  defines program
    ConstructFromComponentsDemo()
      stdout <- Stdout()

      // Date constructors
      fromComponents <- Date(2024, 06, 15)
      fromEpochDays <- Date(19889)
      fromString <- Date("2024-06-15")
      fromLiteral <- 2024-06-15
      stdout.println(`Components: ${fromComponents}`)
      stdout.println(`Epoch days: ${fromEpochDays}`)
      stdout.println(`String: ${fromString}`)
      stdout.println(`Literal: ${fromLiteral}`)

      // Time constructors
      twoArgs <- Time(10, 30)
      threeArgs <- Time(10, 30, 45)
      fromSecond <- Time(37845)
      timeLiteral <- 10:30:45
      stdout.println(`Two args: ${twoArgs}`)
      stdout.println(`Three args: ${threeArgs}`)
      stdout.println(`From seconds: ${fromSecond}`)
      stdout.println(`Literal: ${timeLiteral}`)
      require threeArgs == timeLiteral

      // DateTime from Date (3 args OK)
      dateOnly <- DateTime(2024, 06, 15)
      stdout.println(`Date only: ${dateOnly}`)

      // DateTime from Date promotion then add time via duration
      dtBase <- DateTime(2024, 06, 15)
      withMinute <- dtBase + PT10H30M
      stdout.println(`With minute: ${withMinute}`)

      withSecond <- dtBase + PT10H30M45S
      stdout.println(`With second: ${withSecond}`)

      // From Date (promotes)
      datePart <- 2024-06-15
      fromDate <- DateTime(datePart)
      stdout.println(`From Date: ${fromDate}`)

      // Dynamic component construction
      year <- 2024
      month <- 12
      dayVal <- 25
      christmas <- Date(year, month, dayVal)
      stdout.println(`Dynamic: ${christmas}`)

      // Invalid components produce unset
      badMonth <- Date(2024, 13, 01)
      badHour <- Time(25, 00)
      require ~badMonth?
      require ~badHour?
      stdout.println(`Bad month isSet: ${badMonth?}`)
      stdout.println(`Bad hour isSet: ${badHour?}`)

build component constant construct constructor create date day duration hour literal minute month second time timezone year

Explanation: Renaming the variable means later references to 'fromComponents' become unresolved, triggering E50001. See ek9 -h E50001 for details.


Question: How does Time wrapping work in EK9?

What happens when Time goes past midnight?

How does Time arithmetic handle overflow?

Answer:
Time wraps at the 24-hour boundary. Adding time past midnight wraps to the next day's time. Time is a circular clock, not a linear timeline.

BASIC WRAPPING
  22:00 + PT3H = 01:00 (wraps past midnight)
  01:00 - PT3H = 22:00 (wraps backwards past midnight)
Time always stays within 00:00:00 to 23:59:59.

WHY WRAPPING
Time represents a position on the clock face, not an absolute point in time. A clock does not stop at midnight; it wraps to 00:00. This models the real world correctly: if it is 22:00 and you wait 3 hours, the clock shows 01:00.

WRAPPING WITH INCREMENT/DECREMENT
  lateNight <- 23:59:59
  lateNight++ wraps to 00:00:00
  early <- 00:00:00
  early-- wraps to 23:59:59

WRAPPING AND SUBTRACTION
Subtracting a later time from an earlier time produces a negative Duration:
  gap <- 01:00 - 22:00
  gap is PT-21H (not PT3H)
This is the linear difference, not the 'clock distance'.

DATETIME DOES NOT WRAP
DateTime (which includes a date) does NOT wrap. Adding PT3H to a DateTime at 22:00 moves to 01:00 on the NEXT DAY. The date changes.

WHEN TO USE TIME VS DATETIME
Time: schedules, recurring daily events, clock positions.
DateTime: events with specific dates, timestamps, logging.

See Q31 for Time basics. See Q540 for date arithmetic. See Q539 for time subtraction. See Q550 for calendar edge cases.

defines module qa.time.wrapping

  defines program
    TimeWrappingDemo()
      stdout <- Stdout()

      // Basic wrapping past midnight
      evening <- 22:00
      pastMidnight <- evening + PT3H
      stdout.println(`22:00 + PT3H = ${pastMidnight}`)

      // Wrapping backwards past midnight
      earlyMorning <- 01:00
      lastNight <- earlyMorning - PT3H
      stdout.println(`01:00 - PT3H = ${lastNight}`)

      // Wrapping with larger durations
      noon <- 12:00
      plusDay <- noon + PT24H
      stdout.println(`12:00 + PT24H = ${plusDay}`)

      plusDayHalf <- noon + PT36H
      stdout.println(`12:00 + PT36H = ${plusDayHalf}`)

      // Increment wrapping via duration
      almostMidnight <- 23:59:59
      almostMidnight += PT1S
      stdout.println(`23:59:59 + 1s = ${almostMidnight}`)

      // Decrement wrapping via duration
      justPastMidnight <- 00:00:00
      justPastMidnight -= PT1S
      stdout.println(`00:00:00 - 1s = ${justPastMidnight}`)

      // Subtraction gives linear difference (not clock distance)
      negGap <- 01:00 - 22:00
      stdout.println(`01:00 - 22:00 = ${negGap}`)

      posGap <- 22:00 - 01:00
      stdout.println(`22:00 - 01:00 = ${posGap}`)

      // DateTime does NOT wrap - date advances instead
      eveningDT <- 2024-06-15T22:00:00Z
      nextDayDT <- eveningDT + PT3H
      stdout.println(`DateTime 22:00 + 3h = ${nextDayDT}`)
      // The date changes from June 15 to June 16
      stdout.println(`Day changed: ${nextDayDT.day()}`)

      // startOfDay and endOfDay
      dayStart <- Time().startOfDay()
      dayEnd <- Time().endOfDay()
      stdout.println(`Start of day: ${dayStart}`)
      stdout.println(`End of day: ${dayEnd}`)

Java: LocalTime wraps at midnight (plusHours past midnight wraps). Python: time does not support arithmetic (must use datetime). JavaScript: no Time type, Date wraps dates. Go: no separate Time type, time.Time includes date. Rust: chrono NaiveTime wraps at midnight. EK9: Time wraps at 24h boundary, DateTime does not wrap (date advances).

24 hour
arithmetic
circular
clock
duration
midnight
overflow
time
timezone
wrap
wrapping

dayStart <- Time.MIN
      dayEnd <- Time.MAX

Explanation: EK9 has no static field access like Java's Time.MIN or Time.MAX. Type names without () are not resolved as expressions. Use Time().startOfDay() and Time().endOfDay() factory methods. See ek9 -h E50001 for details.


Question: How does EK9 detect dead code and redundant conditions?

How does EK9 find unreachable code in if statements?

Why does the compiler reject my redundant condition?

Answer:
EK9 tracks variable values through control flow and detects conditions whose outcome is already determined. This catches dead code that other languages silently accept.

FLOW-SENSITIVE VALUE TRACKING
The compiler tracks what is known about each variable at every program point. When a variable is assigned a constant or narrowed by a condition, the compiler remembers that information on each branch.

CONDITION ALWAYS TRUE (E08086)
If the compiler can prove a condition will always be satisfied, it flags it. The else branch would be dead code. Examples: checking x > 5 when x is already known to be greater than 10, or checking x <> 5 in the else branch of 'if x == 5' (the else already implies x is not 5).

CONDITION ALWAYS FALSE (E08087)
If the compiler can prove a condition can never be satisfied, it flags it. The if body would be dead code. Examples: checking x == 5 in the else branch of 'if x == 5' (the else already implies x is not 5), or checking x < 0 after throwing on negative values.

WHAT THE COMPILER TRACKS
Integer range constraints from comparisons (x > 10 narrows to Range(11, MAX)). Equality exclusions from else branches (else of x == 5 means x is anything except 5). Constant values from assignments and named constants. Boolean values from if/else narrowing.

POST-THROW NARROWING
When an if branch throws, the code after it only executes when the condition was false. The compiler narrows accordingly: after 'if x < 0 throw', the compiler knows x >= 0.

COMPOUND CONDITIONS
The compiler also detects redundant clauses in AND/OR expressions: 'x > 10 and x > 5' has a redundant clause, and 'x > 5 or x <= 5' covers the entire domain.

See Q558 for specific tautological condition patterns and how to fix them. See Q311 for the full quality checks catalog. See Q318 for self-comparison detection.

defines module qa.codequality.deadcode

  defines constant

    ADULT_AGE <- 18

    RETIREMENT_AGE <- 65

    FREEZING_POINT <- 0

  defines function

    computeAge()
      <- rtn as Integer: 30

    <?-
      Correct: each branch tests a genuinely different condition.
      The compiler would reject 'if age > 18; if age > 10' because
      the inner check is always true when the outer is true.
    -?>
    classifyLifeStage() as pure
      -> age as Integer
      <- stage as String: "child"

      if age >= RETIREMENT_AGE
        stage: "retired"
      else if age >= ADULT_AGE
        stage: "adult"

    <?-
      Correct: after a throw, the remaining code has narrowed constraints.
      The compiler accepts the second check because temperature was
      reassigned from a function call (unknown value).
    -?>
    validateAndClassify()
      -> rawTemperature as Integer
      <- classification as String: "above-freezing"

      if rawTemperature < FREEZING_POINT
        throw Exception("temperature below freezing")

      adjustedTemp <- computeAge()
      if adjustedTemp < FREEZING_POINT
        classification: "adjusted-below-freezing"

  defines program

    DeadCodeDetectionDemo()
      stdout <- Stdout()

      personAge <- 42
      lifeStage <- classifyLifeStage(personAge)
      stdout.println(`Age ${personAge}: ${lifeStage}`)

      sensorReading <- 15
      tempClass <- validateAndClassify(sensorReading)
      stdout.println(`Temperature ${sensorReading}: ${tempClass}`)

Java: no flow-sensitive tautology detection in the compiler. SpotBugs has limited constant condition checks. SonarQube detects some always-true conditions but is optional. Rust: compiler warns on some unreachable patterns but not flow-sensitive value tracking. Go: no tautology detection. Python: no dead code detection. C++: some compilers warn on constant conditions with -Wall but not data-flow-based. EK9: mandatory flow-sensitive analysis that tracks values through assignments, branches, and throws.

E08086 E08087 always clean-code code condition dead false flow metric quality redundant tautology true unreachable

Explanation: Comparing two literal values produces a compile-time constant, making the condition pointless dead code. Use variables or named constants. See ek9 -h E08082 for details.

Explanation: Declaring a variable that is never used is dead code. Every variable must be referenced after declaration. See ek9 -h E08090 for details.


Question: What are tautological conditions and how do I fix them?

Why does the compiler say my condition is always true?

How do I fix a redundant condition in an if statement?

Why is my boolean comparison flagged as tautological?

Answer:
A tautological condition is one whose outcome is already determined at compile time. EK9 detects these as dead code indicators.

CONSTANT COMPARISON (E08082)
Comparing two literals produces a known result: '3 > 2' is always true. Use a named constant or computed value instead.

CONSTANT ARITHMETIC (E08083)
Arithmetic on two literals produces a constant: '3 + 2' should be expressed as the constant 5. Use a named constant.

REDUNDANT BOOLEAN COMPARISON (E08084)
Comparing a Boolean expression with a literal is redundant: 'if flag == true' should be 'if flag'. The comparison adds nothing.

LOGICAL TAUTOLOGY (E08085)
Logical expressions that always produce the same result: 'x or not x' is always true. 'x and not x' is always false.

FLOW-SENSITIVE CONDITIONS (E08086/E08087)
When the compiler tracks variable values through control flow, it can prove some conditions are predetermined. After 'if x > 10', checking 'x > 5' inside that branch is always true (E08086). In the else branch of 'if x == 5', checking 'x == 5' is always false (E08087).

CONSTANT COALESCING (E08094)
Coalescing operators between two literals produce a known result: '5 <? 3' is always 3. The coalescing operators (<?, >?, <=?, >=?) return a value, not a Boolean, but when both operands are literals the result is still predetermined. Use a variable or named constant instead.

COMMON PATTERNS THAT TRIGGER
Redundant else re-check: 'if x == 5 ... else if x == 5' — the else already implies x is not 5. Post-throw re-check: after 'if x < 0 throw', checking 'x < 0' again is always false. Implied range: 'if x > 100 ... if x > 10' — the inner check is subsumed by the outer.

HOW TO FIX
Use named constants instead of raw literals. Remove redundant inner conditions. Use else-if chains where each branch tests a genuinely different range. Trust post-throw narrowing instead of re-checking.

See Q557 for flow-sensitive dead code detection. See Q311 for the full quality checks catalog. See Q318 for self-comparison detection. See Q637 for safe variable comparison patterns. See Q638 for named constant patterns. See Q639 for Boolean usage without literals. See Q640 for collection isSet check patterns.

defines module qa.codequality.tautology

  defines constant

    MINIMUM_AGE <- 18

    MAXIMUM_AGE <- 120

    SPEED_LIMIT <- 65

    PENALTY_THRESHOLD <- 80

    DANGER_THRESHOLD <- 100

  defines function

    computeAge()
      <- rtn as Integer: 25

    <?-
      Correct: each branch tests a genuinely different range.
      The compiler would reject 'if age >= 18; if age >= 10' because
      the inner check would be always true when the outer is true.
    -?>
    classifyAge() as pure
      -> age as Integer
      <- category as String: "minor"

      if age >= MAXIMUM_AGE
        category: "centenarian"
      else if age >= MINIMUM_AGE
        category: "adult"

    <?-
      Correct: using named constants for comparisons avoids E08082.
      Raw literals like 'if speed > 65' would trigger magic literal detection.
    -?>
    classifySpeed() as pure
      -> speed as Integer
      <- category as String: "safe"

      if speed >= DANGER_THRESHOLD
        category: "dangerous"
      else if speed >= PENALTY_THRESHOLD
        category: "ticket-worthy"
      else if speed > SPEED_LIMIT
        category: "over-limit"

    <?-
      Correct: uses a Boolean directly instead of comparing with true/false.
      Writing 'if isEnabled == true' would trigger E08084.
    -?>
    formatFlag() as pure
      -> isEnabled as Boolean
      <- label as String: "disabled"

      if isEnabled
        label: "enabled"

    <?-
      Correct: after throwing on invalid input, the remaining code
      can trust the variable satisfies the inverse condition without re-checking.
    -?>
    validateAndProcess()
      -> score as Integer
      <- result as String: "processed"

      if score < 0
        throw Exception("score must not be negative")

      adjustedScore <- computeAge()
      if adjustedScore < 0
        result: "adjusted-score-negative"

  defines program

    TautologicalConditionsDemo()
      stdout <- Stdout()

      personAge <- 42
      ageCategory <- classifyAge(personAge)
      stdout.println(`Age ${personAge}: ${ageCategory}`)

      carSpeed <- 72
      speedCategory <- classifySpeed(carSpeed)
      stdout.println(`Speed ${carSpeed}: ${speedCategory}`)

      featureOn <- true
      flagLabel <- formatFlag(featureOn)
      stdout.println(`Feature: ${flagLabel}`)

      testScore <- 85
      outcome <- validateAndProcess(testScore)
      stdout.println(`Score ${testScore}: ${outcome}`)

Java: no tautological condition detection in javac. SpotBugs has limited checks. SonarQube detects some constant conditions. Rust: clippy has logic_bug and redundant_pattern lints. Go: no tautology detection. Python: pylint detects some constant conditions. C++: -Wtautological-compare warns on a few patterns. EK9: comprehensive detection covering constant comparison, constant coalescing, boolean redundancy, logical tautology, and flow-sensitive narrowing, all as mandatory compiler errors.

Explanation: Comparing a Boolean to the literal true is redundant. The Boolean IS the condition. Use it directly. See ek9 -h E08084 for details.

Explanation: Comparing two literal values produces a compile-time constant. Use named constants compared against variables. See ek9 -h E08082 for details.


Question: Why does EK9 reject my isSet check as redundant?

How does EK9 track set and unset variables?

When is the ? operator genuinely needed?

Answer:
EK9 tracks whether each variable is set, unset, or unknown at every program point. When the compiler can prove the outcome of an isSet check, it flags it as redundant.

REDUNDANT ISSET CHECK (E08088)
If a variable is known to be set, checking it with ? is pointless. Examples: a variable assigned from a literal ('name <- hello; if name?'), a variable inside a guard block ('if name <- getName(); if name?' — the guard already proved it is set), or a variable assigned from a constructor ('list <- List(); if list?').

NEVER-SET ISSET CHECK (E08089)
If a variable is declared but never assigned, checking it with ? is always false. Example: 'name as String?; if name?' — the variable was declared unset and no assignment occurs before the check.

WHEN ISSET IS GENUINELY NEEDED
The ? operator is needed when the variable might or might not be set depending on runtime conditions. A value from a function call may be unset. A value from a Dict lookup may be absent. A value conditionally assigned in only one branch of an if statement needs checking afterward.

GUARD PATTERN REPLACES ISSET
Instead of 'result <- lookup(key); if result?', use the guard pattern: 'if result <- lookup(key)'. The guard combines the call and the isSet check into one expression, and the variable is only in scope when it is known to be set.

See Q557 for flow-sensitive dead code detection. See Q558 for tautological condition patterns. See Q311 for the full quality checks catalog. See Q640 for collection isSet check patterns.

defines module qa.codequality.issetcheck

  defines function

    fetchName()
      <- rtn as String: "Alice"

    fetchOptionalTitle()
      <- rtn as String: String()

    <?-
      Correct: isSet check on a value from a function call is genuinely useful
      because the function might return an unset value.
    -?>
    greetPerson()
      -> name as String
      <- greeting as String: `Hello ${name}`

      title <- fetchOptionalTitle()
      if title?
        greeting: `Hello ${title} ${name}`

    <?-
      Correct: the guard pattern combines lookup and isSet check.
      The variable is only in scope when it is known to be set.
    -?>
    greetWithGuard()
      <- greeting as String: "Hello stranger"

      if name <- fetchName()
        greeting: `Hello ${name}`

    <?-
      Correct: isSet check on a value from another function call
      where the result might be unset. The compiler does not know
      the runtime return value, so the check is genuinely needed.
    -?>
    buildFullGreeting()
      -> name as String
      <- result as String: `Greeting for ${name}`

      optionalSuffix <- fetchOptionalTitle()
      if optionalSuffix?
        result: `Dear ${optionalSuffix} ${name}`

  defines program

    RedundantIsSetDemo()
      stdout <- Stdout()

      personGreeting <- greetPerson("Bob")
      stdout.println(personGreeting)

      guardGreeting <- greetWithGuard()
      stdout.println(guardGreeting)

      fullGreeting <- buildFullGreeting("Carol")
      stdout.println(fullGreeting)

Java: no compiler tracking of null vs non-null at each program point. Optional.isPresent() is never flagged as redundant. Kotlin: smart casts track nullability but do not flag redundant null checks. Rust: Option matching is exhaustive but redundant Some checks are not flagged. Go: no nil tracking. Python: no None tracking. C++: no nullptr flow analysis. EK9: mandatory flow-sensitive tracking of set/unset state with compiler errors for redundant or always-failing checks.

E08088 E08089 check clean-code flow guard isSet isset metric null-safe quality redundant safe set tracking unset variable

Explanation: Declaring a variable and never using it is dead code. Every variable must be referenced. See ek9 -h E08090 for details.


Question: What does 'as pure' mean on a method in EK9?

What restrictions apply to pure methods?

Answer:
The 'as pure' modifier on a method guarantees that it will not mutate any state through mutation operators. This is a compiler-enforced contract, not a convention.

WHAT PURE MEANS
A pure method cannot use mutation operators (+=, -=, *=, /=, :=:, :~:, :^:). It can reassign local variables with : (which creates a new value). It cannot call non-pure methods. The compiler enforces all of these rules.

HOW TO DECLARE PURE
Add 'as pure' after the method name:
  calculate() as pure
    -> x as Integer
    <- rtn as Integer: x + 1

WHAT PURE ALLOWS
Reassignment with : (creates new binding, not mutation). Creating new values with non-mutating operators (+, -, *, /). Reading fields and parameters. Returning computed values.

WHAT PURE FORBIDS
Mutation operators (+=, -=, *=, /=, :=:, :~:, :^:) are forbidden (E08120). Calling non-pure functions or methods is forbidden (E08130). These are compile-time errors, not runtime checks.

WHY PURE MATTERS
Pure methods are safe to call from any context. They cannot corrupt shared state. They enable compiler optimizations. They create auditable security boundaries.

See Q273 for purity as a security boundary. See Q54 for pure functions and Consumer vs Acceptor. See Q561 for purity inheritance rules. See Q566 for pure call chain restrictions. See Q612 for dispatcher purity matching requirements. See Q635 for forbidden mutation operators in pure methods.

See Q666 for injection in pure context.
See Q678 for purity override contract. See Q690 for pure call chain.

defines module qa.purity.basics

  defines class

    Calculator
      factor <- 1

      Calculator()
        -> factor as Integer
        this.factor: factor

      //A pure method: no mutation, just computation
      compute() as pure
        -> inputValue as Integer
        <- result as Integer: inputValue * factor

      //A pure accessor: reads state without modifying it
      currentFactor() as pure
        <- rtn as Integer: factor

      //Non-pure method: uses mutation operator
      adjustFactor()
        -> delta as Integer
        factor += delta

      default operator ?

  defines program

    PureMethodBasicsDemo()
      stdout <- Stdout()

      calc <- Calculator(3)
      stdout.println(`Factor: ${calc.currentFactor()}`)

      result <- calc.compute(10)
      stdout.println(`3 * 10 = ${result}`)

      //Non-pure: mutates factor
      calc.adjustFactor(2)
      stdout.println(`After adjust: ${calc.currentFactor()}`)

      result2 <- calc.compute(10)
      stdout.println(`5 * 10 = ${result2}`)

Java: no purity concept, any method can mutate anything. Python: no purity enforcement. Haskell: all functions pure by default (IO monad for effects). Rust: immutable borrows prevent mutation but no purity marker. Kotlin: no purity concept. EK9: 'as pure' modifier enforces no mutation operators and no non-pure calls at compile time.

contract enforce immutable method mutation operator pure purity side-effect

pure
pure function
pure method
side effect free

Explanation: Renaming the variable means later references to 'calc' become unresolved, triggering E50001. See ek9 -h E50001 for details.

Explanation: Renaming the variable means later references to 'result' become unresolved, triggering E50001. See ek9 -h E50001 for details.


Question: Why must an override of a pure method also be pure?

What happens if I override a pure method without 'as pure'?

How does purity work with method overriding?

Answer:
Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure).

When a parent method is marked 'as pure', every override of that method must also be marked 'as pure'. The compiler enforces this with error E05150.

WHY THIS RULE EXISTS
Callers of the parent type expect pure behavior. If a child could drop purity, polymorphic calls would break the contract:
  parent as Shape: Circle()
  parent.area()  // Caller expects pure, child must honour that

THE ERROR: E05150
If the parent method is pure and the child override is not pure, the compiler reports E05150: the parent method is pure so the override must also be pure.

CORRECT PATTERN
Always add 'as pure' to the override when the parent is pure:
  override area() as pure
    <- rtn as Float: ...

BEHAVIORAL SUBSTITUTION
This enforces the Liskov Substitution Principle for purity. Anywhere a Parent is used, a Child can replace it without violating purity expectations.

See Q560 for pure method basics. See Q562 for the opposite rule (cannot add pure). See Q567 for purity through inheritance chains. See Q568 for pure operators and overrides.

defines module qa.purity.overriderequired

  defines class

    Shape as abstract
      //Pure method in parent: all overrides must also be pure
      area() as pure abstract
        <- rtn as Float?

      describe() as pure abstract
        <- rtn as String?

      default operator ?

    Circle extends Shape
      radius <- 0.0

      Circle()
        -> radius as Float
        this.radius: radius

      //Correct: override of pure method is also pure
      override area() as pure
        <- rtn as Float: radius * radius * 3.14159

      override describe() as pure
        <- rtn as String: `Circle with radius ${radius}`

      default operator ?

    Square extends Shape
      side <- 0.0

      Square()
        -> side as Float
        this.side: side

      //Correct: pure override matches parent purity
      override area() as pure
        <- rtn as Float: side * side

      override describe() as pure
        <- rtn as String: `Square with side ${side}`

      default operator ?

  defines program

    PureOverrideRequiredDemo()
      stdout <- Stdout()

      //Polymorphic use: callers trust pure contract
      shapes <- List() of Shape
      shapes += Circle(5.0)
      shapes += Square(4.0)

      for shape in shapes
        stdout.println(`${shape.describe()}: area = ${shape.area()}`)

Java: no purity concept, method contracts are documentation only. Kotlin: no purity enforcement. Rust: trait methods have no purity marker. EK9: compiler-enforced purity inheritance via E05150.

E05150 abstract child contract function immutable inheritance open override parent pure purity side-effect substitution virtual

Explanation: The parent Shape declares area as pure abstract. Overriding without as pure violates the purity contract. All overrides of pure methods must also be pure. See ek9 -h E05150 for details.

Explanation: When the parent Shape has an abstract describe method, the child must use the override keyword to replace it. Omitting override triggers shadowing detection. See ek9 -h E05120 for details.


Question: Why can't I add 'as pure' to an override when the parent method isn't pure?

Can I make a child method pure if the parent is not?

Why does EK9 prevent adding purity in an override?

Answer:
You cannot add 'as pure' to an override when the parent method is not pure. The compiler reports E05160.

WHY YOU CANNOT ADD PURE
Callers using the parent type expect the method MAY have side effects. If the child adds purity, code using a parent reference cannot rely on it because another child might not be pure. Purity is a contract from the top of the hierarchy, not something added partway down.

THE ERROR: E05160
If you mark an override 'as pure' but the parent method is not pure, the compiler reports E05160: the parent method is not pure so you cannot add purity.

CORRECT APPROACH
Option 1: Remove 'as pure' from the override. The child matches the parent contract.
Option 2: If you control the parent, make the parent method pure. Then all children must follow.

DESIGN PRINCIPLE
Purity flows DOWN the hierarchy, not UP. The root type decides whether the contract is pure. Children honour it; they cannot change it in either direction.

See Q561 for the reverse rule (must keep pure). See Q560 for pure method basics. See Q567 for purity through inheritance chains.

defines module qa.purity.cannotaddpure

  defines class

    //Parent is NOT pure: children must match
    Logger as open
      logMessage()
        -> message as String
        <- formatted as String: "[LOG] " + message

      default operator ?

    //Correct: child does NOT add pure
    TimestampLogger extends Logger
      override logMessage()
        -> message as String
        <- formatted as String: "[LOG:TS] " + message

      default operator ?

    //Separate pure hierarchy: designed pure from the start
    Formatter as abstract
      format() as pure abstract
        -> text as String
        <- rtn as String?

      default operator ?

    UpperFormatter extends Formatter
      override format() as pure
        -> text as String
        <- rtn as String: text.upperCase()

      default operator ?

  defines program

    CannotAddPureDemo()
      stdout <- Stdout()

      //Non-pure hierarchy
      logger <- TimestampLogger()
      stdout.println(logger.logMessage("system started"))

      //Pure hierarchy (designed pure from the start)
      formatter <- UpperFormatter()
      stdout.println(formatter.format("hello"))

Java: no purity concept, no enforcement. Kotlin: no purity. Rust: no purity on trait methods. Go: no purity. EK9: purity is a hierarchical contract enforced by E05160 (cannot add) and E05150 (cannot remove).

E05160 abstract add contract forbidden function hierarchy immutable open override parent pure purity side-effect virtual

Explanation: The parent Logger.logMessage is not pure. Adding as pure to the override is forbidden because purity flows down from the top of the hierarchy, not up. See ek9 -h E05160 for details.

Explanation: The parent Formatter.format is declared pure abstract. Overriding without as pure would violate the purity contract. See ek9 -h E05150 for details.


Question: How do pure constructors work and why use ':=?' for field assignment?

Why does my pure constructor reject ':' for field assignment?

How do I assign fields in a pure constructor?

Answer:
Pure constructors use ':=?' (guarded assignment) for field assignment, not ':' (direct assignment). The ':=?' operator only assigns if the variable is currently unset, preventing mutation.

WHY ':=?' IN PURE CONSTRUCTORS
In a pure constructor, mutation operators are forbidden (E08120). The ':' operator on a field that already has a default value counts as reassignment. The ':=?' operator is safe because it only assigns when the field is unset.

PATTERN FOR PURE CONSTRUCTORS
Declare fields with default values, then use ':=?' in the constructor:
  Config
    host as String: String()
    port as Integer: Integer()
    Config() as pure
      -> host as String, port as Integer
      this.host :=? host
      this.port :=? port

WHY THIS WORKS
The fields must start UNSET - 'String()', 'Integer()' - and NOT a value such as 0 or "". A field given a value is already SET, so ':=?' can never fire and the constructor argument is silently discarded; the compiler rejects that as E08095. The ':=?' checks if the field is unset and assigns only then. After the first assignment, the field is set and ':=?' becomes a no-op. This is idempotent and non-mutating.

ALL OR NOTHING RULE
If ANY constructor is pure, ALL constructors must be pure (see Q564). This prevents inconsistent construction semantics.

See Q560 for pure method basics. See Q564 for all-or-nothing constructor purity. See Q565 for multiple pure constructors. See Q79 for guarded assignment operator.

defines module qa.purity.constructorassign

  defines class

    Config
      host as String: String()
      port as Integer: Integer()

      //Pure constructor using :=? for field assignment
      Config() as pure
        ->
          host as String
          port as Integer
        this.host :=? host
        this.port :=? port

      host() as pure
        <- rtn as String: host

      port() as pure
        <- rtn as Integer: port

      default operator ?

    //Another example: immutable value object
    Point
      xCoord as Float: Float()
      yCoord as Float: Float()

      Point() as pure
        ->
          xCoord as Float
          yCoord as Float
        this.xCoord :=? xCoord
        this.yCoord :=? yCoord

      xCoord() as pure
        <- rtn as Float: xCoord

      yCoord() as pure
        <- rtn as Float: yCoord

      distanceFromOrigin() as pure
        <- rtn as Float: sqrt(xCoord * xCoord + yCoord * yCoord)

      default operator ?

  defines program

    PureConstructorAssignDemo()
      stdout <- Stdout()

      config <- Config("localhost", 8080)
      stdout.println(`Host: ${config.host()}, Port: ${config.port()}`)

      point <- Point(3.0, 4.0)
      stdout.println(`Point: (${point.xCoord()}, ${point.yCoord()})`)
      stdout.println(`Distance: ${point.distanceFromOrigin()}`)

Java: no pure constructors, no guarded assignment. Python: no purity concept. Rust: constructors are always pure (no mutation after move). Kotlin: no pure constructors. EK9: pure constructors with ':=?' for idempotent field initialization, E08120 prevents mutation in pure context.

E08120 assign constant constructor contract field guarded idempotent immutable isset mutation null-safe pure purity safe side-effect

Explanation: Giving the field a value at declaration makes it SET, so the ':=?' in the constructor can never apply and the 'host' argument is silently discarded - the object keeps "localhost" whatever you construct it with. Fields assigned by a pure constructor must be declared UNSET. See ek9 -h E08095 for details.

Explanation: In a pure constructor, direct assignment with : counts as reassignment of a field. The guarded assignment :=? must be used instead because it only assigns when the field is unset. See ek9 -h E08100 for details.

Explanation: Renaming the variable means later references to 'config' become unresolved, triggering E50001. See ek9 -h E50001 for details.


Question: Why must all constructors be pure if any one is pure?

What is E05190 mix of pure and not pure constructors?

Can I have some pure and some non-pure constructors?

What is the all-or-nothing constructor purity rule?

Answer:
EK9 enforces an all-or-nothing rule for constructor purity. If any constructor in a class is marked 'as pure', then every constructor must be marked 'as pure'. The compiler reports E05190 for violations.

WHY ALL OR NOTHING
Factory methods and delegation chains might call any constructor. If purity is inconsistent, the caller cannot know whether construction had side effects. Consistent purity makes object creation predictable.

THE ERROR: E05190
If one constructor is pure and another is not, the compiler reports E05190: mix of pure and not pure constructors.

CORRECT PATTERN: ALL PURE
Make every constructor pure if you want pure construction:
  MyClass() as pure
    -> a as String
    ...
  MyClass() as pure
    -> a as String, b as Integer
    ...

CORRECT PATTERN: NONE PURE
Make no constructors pure if construction needs side effects:
  MyClass()
    -> a as String
    ...
  MyClass()
    -> a as String, b as Integer
    ...

See Q563 for pure constructor field assignment. See Q565 for multiple pure constructors. See Q586 for pure constructor delegation.

defines module qa.purity.allornothing

  defines class

    //Correct: ALL constructors are pure
    Coordinate
      xPos as Float: Float()
      yPos as Float: Float()

      Coordinate() as pure
        ->
          xPos as Float
          yPos as Float
        this.xPos :=? xPos
        this.yPos :=? yPos

      //Second pure constructor: also uses :=?
      Coordinate() as pure
        -> xPos as Float
        this.xPos :=? xPos

      xPos() as pure
        <- rtn as Float: xPos

      yPos() as pure
        <- rtn as Float: yPos

      default operator ?

    //Correct: NO constructors are pure
    LoggedEntity
      entityName <- String()

      LoggedEntity()
        -> entityName as String
        this.entityName: entityName

      LoggedEntity()
        ->
          entityName as String
          prefix as String
        this.entityName: prefix + entityName

      entityName() as pure
        <- rtn as String: entityName

      default operator ?

  defines program

    AllOrNothingDemo()
      stdout <- Stdout()

      //All-pure construction
      coord1 <- Coordinate(3.0, 4.0)
      coord2 <- Coordinate(5.0)
      stdout.println(`Coord1: (${coord1.xPos()}, ${coord1.yPos()})`)
      stdout.println(`Coord2: (${coord2.xPos()}, ${coord2.yPos()})`)

      //No-pure construction
      entity1 <- LoggedEntity("server")
      entity2 <- LoggedEntity("server", "prod-")
      stdout.println(`Entity1: ${entity1.entityName()}`)
      stdout.println(`Entity2: ${entity2.entityName()}`)

Java: no constructor purity concept. Python: no purity. Rust: constructors are always effectively pure. Kotlin: no constructor purity. EK9: all-or-nothing constructor purity enforced by E05190.

E05190 all-or-nothing consistent constant constructor contract factory immutable mix pure purity side-effect

Coordinate() as pure
        -> xPos as Float

Explanation: Since the two-parameter Coordinate constructor is pure, the one-parameter constructor must also be pure. Mixing pure and non-pure constructors triggers E05190. See ek9 -h E05190 for details.

Explanation: Renaming the variable means later references to 'coord1' become unresolved, triggering E50001. See ek9 -h E50001 for details.


Question: How do I write multiple pure constructors with delegation?

Can pure constructors use this() delegation?

What is the pattern for overloaded pure constructors?

Answer:
Multiple pure constructors can delegate to each other using this(), just like non-pure constructors. The delegated-to constructor must also be pure (since all constructors must have consistent purity).

DELEGATION IN PURE CONSTRUCTORS
Pure constructors can call this() as the first statement:
  Address() as pure
    -> street as String
    this(street, "Unknown")
  Address() as pure
    -> street as String, city as String
    this.street :=? street
    this.city :=? city
The delegating constructor calls another pure constructor.

WHY DELEGATION WORKS
Since E05190 requires all constructors to be consistently pure, the target of this() is guaranteed to be pure. No purity violation can occur.

PATTERN: DEFAULT VALUES
Use delegation to provide sensible defaults:
  Config() as pure
    this("localhost", 8080)
  Config() as pure
    -> host as String
    this(host, 8080)
  Config() as pure
    -> host as String, port as Integer
    this.host :=? host
    this.port :=? port

See Q563 for pure constructor field assignment. See Q564 for all-or-nothing purity. See Q580 for this() delegation in general. See Q586 for pure constructor delegation with super().

defines module qa.purity.multiplepure

  defines class

    Address
      street <- String()
      city <- String()
      postcode <- String()

      //Minimal constructor delegates to two-arg
      Address() as pure
        -> street as String
        this(street, "Unknown")

      //Two-arg delegates to three-arg
      Address() as pure
        ->
          street as String
          city as String
        this(street, city, "N/A")

      //Full constructor: assigns all fields
      Address() as pure
        ->
          street as String
          city as String
          postcode as String
        this.street :=? street
        this.city :=? city
        this.postcode :=? postcode

      street() as pure
        <- rtn as String: street

      city() as pure
        <- rtn as String: city

      postcode() as pure
        <- rtn as String: postcode

      default operator ?

  defines program

    MultiplePureConstructorsDemo()
      stdout <- Stdout()

      addr1 <- Address("123 High St")
      stdout.println(`${addr1.street()}, ${addr1.city()}, ${addr1.postcode()}`)

      addr2 <- Address("456 Low St", "London")
      stdout.println(`${addr2.street()}, ${addr2.city()}, ${addr2.postcode()}`)

      addr3 <- Address("789 Mid St", "Paris", "75001")
      stdout.println(`${addr3.street()}, ${addr3.city()}, ${addr3.postcode()}`)

Java: constructor chaining with this() works but no purity concept. Kotlin: constructor delegation but no purity enforcement. EK9: this() delegation in pure constructors, all constructors must be pure (E05190), :=? for field assignment.

E05190 chain constant constructor contract default delegation immutable overload pure purity side-effect this

Address()
        -> street as String
        this(street, "Unknown")

Explanation: All constructors must be consistently pure. If the full three-argument constructor is pure, the delegating one-argument constructor must also be declared pure. See ek9 -h E05190 for details.

Explanation: In a pure constructor, direct assignment with : counts as reassignment. The guarded assignment :=? must be used for field initialization to maintain purity. See ek9 -h E08100 for details.


Question: What can and cannot be called from a pure method?

What is E08130 non-pure call in pure scope?

Can a pure method call a non-pure method?

Answer:
A pure method can only call other pure methods and functions. Calling a non-pure method from a pure context produces error E08130.

THE RULE
Pure can only call pure. If method A is pure, every method A calls must also be pure. This creates a trust chain: if the entry point is pure, the entire execution path is pure.

THE ERROR: E08130
Calling a non-pure function or method from within a pure context produces E08130: non-pure call in pure scope.

WHAT PURE CAN CALL
Other pure methods on the same class. Pure methods on other objects. Built-in pure operators (+, -, *, /, comparisons). Pure functions defined in the module.

WHAT PURE CANNOT CALL
Non-pure methods (E08130). Methods that use mutation operators internally. Functions not marked as pure.

I/O EXCEPTION
Stdout, Stderr, Stdin, and other I/O types carry the IO marker trait. Methods on IO types are allowed in pure context because I/O is considered a separate concern from data mutation.

BUILT-IN PURE METHODS
Most accessor methods on built-in types (length, upperCase, contains) are pure. Mutation methods (+=, append, etc.) are not.

See Q560 for pure method basics. See Q561 for purity in overrides. See Q273 for purity as security boundary. See Q54 for Consumer (pure) vs Acceptor (non-pure).

defines module qa.purity.restrictions

  defines function

    //Pure function: only uses pure operations
    doubleValue() as pure
      -> number as Integer
      <- result as Integer: number + number

    //Pure function calling another pure function
    quadrupleValue() as pure
      -> number as Integer
      <- result as Integer: doubleValue(doubleValue(number))

  defines class

    Processor
      prefix <- String()

      Processor()
        -> prefix as String
        this.prefix: prefix

      //Pure method: calls only pure functions and methods
      transform() as pure
        -> text as String
        <- rtn as String: `${prefix}: ${text.upperCase()}`

      //Pure method calling pure function
      processNumber() as pure
        -> number as Integer
        <- rtn as String: `${prefix}: ${quadrupleValue(number)}`

      //Non-pure method: this is fine, just cannot be called from pure context
      logAndTransform()
        -> text as String
        <- rtn as String: `${prefix}: ${text.upperCase()}`

      default operator ?

  defines program

    PureMethodRestrictionsDemo()
      stdout <- Stdout()

      //Pure function calls
      stdout.println(`Double 5: ${doubleValue(5)}`)
      stdout.println(`Quadruple 5: ${quadrupleValue(5)}`)

      //Pure method calls
      proc <- Processor("APP")
      stdout.println(proc.transform("hello"))
      stdout.println(proc.processNumber(7))

      //Non-pure method call (allowed from program, which is non-pure)
      stdout.println(proc.logAndTransform("world"))

Java: no call chain purity enforcement. Kotlin: no purity tracking. Rust: no purity annotation on function calls. Haskell: pure functions cannot call IO without the monad. EK9: E08130 enforces pure-calls-pure chain, I/O types are exempted via IO trait.

E08130 call chain contract function immutable migrate non-pure pure purity restriction scope side-effect trust

Explanation: Renaming the variable means later references to 'proc' become unresolved, triggering E50001. See ek9 -h E50001 for details.

Explanation: Referencing a variable 'procXYZ' that was never declared triggers E50001. The original variable was 'proc'. See ek9 -h E50001 for details.


Question: How does purity flow through multi-level inheritance chains?

Does purity apply to grandchild classes?

How does the pure contract carry through three levels of inheritance?

Answer:
Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure).

Purity flows through the entire inheritance chain. If a grandparent method is pure, the parent override must be pure, and the grandchild override must also be pure. Every level must maintain the contract.

MULTI-LEVEL RULE
Grandparent declares method as pure. Parent override must be pure (E05150 if not). Grandchild override must be pure (E05150 if not). The chain never breaks.

WHY EVERY LEVEL MATTERS
Code using the grandparent type expects purity. The actual runtime object might be any descendant. Every descendant must honour the original contract.

PRACTICAL PATTERN
Design purity at the root of the hierarchy:
  Animal as abstract
    sound() as pure abstract
  Dog extends Animal
    override sound() as pure
  Labrador extends Dog
    override sound() as pure

CANNOT ADD PURE LATER
If the grandparent is NOT pure, no descendant can add purity (E05160). Purity is designed in at the top.

See Q561 for purity override rules. See Q562 for cannot-add-pure. See Q575 for deep override chains. See Q568 for pure operators.
See Q677 for abstract implementation chain. See Q678 for purity override contract. See Q699 for return type covariance.

defines module qa.purity.chaininheritance

  defines class

    //Level 1: declares pure contract
    Animal as abstract
      sound() as pure abstract
        <- rtn as String?

      describe() as pure abstract
        <- rtn as String?

      default operator ?

    //Level 2: must maintain pure
    Dog extends Animal as open
      override sound() as pure
        <- rtn as String: "Woof"

      override describe() as pure
        <- rtn as String: "Dog"

      default operator ?

    //Level 3: must STILL maintain pure
    Labrador extends Dog
      override sound() as pure
        <- rtn as String: "Woof woof!"

      override describe() as pure
        <- rtn as String: "Labrador"

      default operator ?

    //Another chain: Cat hierarchy
    Cat extends Animal as open
      override sound() as pure
        <- rtn as String: "Meow"

      override describe() as pure
        <- rtn as String: "Cat"

      default operator ?

    Siamese extends Cat
      override sound() as pure
        <- rtn as String: "Mrow!"

      override describe() as pure
        <- rtn as String: "Siamese"

      default operator ?

  defines program

    PureChainDemo()
      stdout <- Stdout()

      //All levels honour the pure contract
      animals <- List() of Animal
      animals += Labrador()
      animals += Siamese()

      for animal in animals
        stdout.println(`${animal.describe()} says: ${animal.sound()}`)

Java: no multi-level purity tracking. Kotlin: no purity. EK9: purity contract is enforced at every level in the inheritance chain via E05150.

E05150 chain contract grandchild grandparent immutable inheritance multi-level pure purity side-effect

Explanation: At every level of the inheritance chain, the pure contract must be maintained. The abstract Animal.sound is pure, so Dog.sound and Labrador.sound must also be pure. See ek9 -h E05150 for details.

override describe() as pure
        <- rtn as String: nonPureHelper()

Explanation: Even though describe is correctly declared as pure, calling a non-pure method from within it would violate the purity contract transitively. See ek9 -h E50050 for details.


Question: How does purity apply to operators and their overrides?

Must I override a pure operator with a pure operator?

How does purity interact with operator overriding?

Answer:
Operators follow the same purity rules as methods. If a parent defines a pure operator, the child override must also be pure (E05150). Many built-in operators are already pure.

PURE OPERATORS
Comparison operators (<=>), equality (==, <>), hash (#?), and string ($) are typically pure. They compute a result without modifying state.

OVERRIDE RULE
If a parent operator is pure, the child override must also be pure:
  override operator <=> as pure
    -> other as MyType
    <- rtn as Integer: ...

OPERATOR ? IS SPECIAL
The operator ? (isSet check) is inherited from the base type. Custom classes use 'override operator ?' and it is typically pure:
  override operator ? as pure
    <- rtn as Boolean: field?

MUTATING OPERATORS
Operators like +=, -=, :=:, :~:, :^: are mutating by nature and should NOT be pure. These modify the object in place.

See Q560 for pure method basics. See Q561 for purity in overrides. See Q245 for custom type operators. See Q241 for mutation operators.

defines module qa.purity.operators

  defines class

    Temperature as open
      degrees <- 0.0

      Temperature()
        -> degrees as Float
        this.degrees: degrees

      degrees() as pure
        <- rtn as Float: degrees

      //Pure comparison operator
      operator <=> as pure
        -> other as Temperature
        <- rtn as Integer: degrees <=> other.degrees

      //Pure equality derived from comparison
      default operator ==

      //Pure string representation
      operator $ as pure
        <- rtn as String: $degrees + " degrees"

      //Pure hash code
      operator #? as pure
        <- rtn as Integer: #? degrees

      //Pure isSet check
      override operator ? as pure
        <- rtn as Boolean: degrees?

    //Child must keep operators pure
    Celsius extends Temperature
      Celsius()
        -> degrees as Float
        super(degrees)

      //Override of pure operator must be pure
      override operator $ as pure
        <- rtn as String: `${degrees()} C`

      default operator ?

  defines program

    PureOperatorsDemo()
      stdout <- Stdout()

      temp1 <- Temperature(20.0)
      temp2 <- Temperature(30.0)
      stdout.println(`${temp1} vs ${temp2}`)
      stdout.println(`Equal: ${temp1 == temp2}`)
      stdout.println(`Compare: ${temp1 <=> temp2}`)

      cel <- Celsius(100.0)
      stdout.println(`Celsius: ${cel}`)

Java: operator overloading not supported, no purity concept. Kotlin: operator overloading exists but no purity enforcement. Rust: trait-based operators, no purity markers. EK9: operators follow identical purity rules to methods, E05150 applies to operator overrides.

E05150 abstract comparison contract equality immutable isSet mutation open operator override pure purity side-effect virtual

Explanation: The parent Temperature.operator $ is pure. The child Celsius override must also be pure. Removing purity from the operator override violates the contract. See ek9 -h E07500 for details.

Explanation: When overriding a parent operator, the override keyword is required. Omitting it on operator $ in Celsius triggers shadowing detection. See ek9 -h E05120 for details.


Question: How do traits interact with pure method contracts?

Must a class implementing a trait maintain purity?

How does purity work with trait default methods?

Answer:
Traits can declare methods as pure, and any class implementing the trait must maintain that purity contract. This is the same E05150 rule that applies to class inheritance.

PURE METHODS IN TRAITS
A trait can declare abstract pure methods:
  defines trait
    Measurable
      measure() as pure abstract
        <- rtn as Float?
Any class implementing Measurable must provide a pure override.

PURE DEFAULT METHODS
Traits can have default method implementations that are pure:
  Describable
    label() as pure
      <- rtn as String: "unknown"
Classes can override the default but must keep it pure.

IMPLEMENTATION RULE
When a class uses 'with trait of' and overrides a pure trait method, the override must be pure (E05150):
  MyClass with trait of Measurable
    override measure() as pure
      <- rtn as Float: 42.0

MULTIPLE TRAITS
If a class implements multiple traits, each pure method contract is independently enforced. A method that satisfies a pure trait must be pure.

See Q106 for trait basics. See Q107 for multiple traits. See Q561 for purity override rules. See Q567 for purity chains.

defines module qa.purity.traitpure

  defines trait

    //Trait requiring pure methods
    Measurable
      measure() as pure abstract
        <- rtn as Float?

    //Trait with pure default method
    Labelable
      label() as pure
        <- rtn as String: "unlabeled"

  defines class

    //Must keep measure() pure because trait requires it
    Box with trait of Measurable, Labelable
      width <- 0.0
      height <- 0.0

      Box()
        ->
          width as Float
          height as Float
        this.width: width
        this.height: height

      //Pure override satisfies Measurable trait
      override measure() as pure
        <- rtn as Float: width * height

      //Pure override satisfies Labelable trait
      override label() as pure
        <- rtn as String: `Box ${width}x${height}`

      default operator ?

    //Another implementation of same traits
    Sphere with trait of Measurable, Labelable
      radius <- 0.0

      Sphere()
        -> radius as Float
        this.radius: radius

      override measure() as pure
        <- rtn as Float: 4.0 * 3.14159 * radius * radius

      override label() as pure
        <- rtn as String: `Sphere r=${radius}`

      default operator ?

  defines program

    TraitPureMethodsDemo()
      stdout <- Stdout()

      box <- Box(3.0, 4.0)
      stdout.println(`${box.label()}: ${box.measure()}`)

      sphere <- Sphere(5.0)
      stdout.println(`${sphere.label()}: ${sphere.measure()}`)

      //Polymorphic use through trait
      items <- List() of Measurable
      items += Box(2.0, 3.0)
      items += Sphere(1.0)

      for item in items
        stdout.println(`Measurement: ${item.measure()}`)

Java: interfaces can have default methods but no purity concept. Kotlin: interfaces with defaults but no purity. Rust: traits with default implementations but no purity markers. EK9: traits can declare pure methods, implementations must maintain purity via E05150.

E05150 abstract contract default immutable implement method pure purity side-effect trait

Explanation: The Measurable trait declares measure as pure abstract. The implementing class must maintain purity. Overriding without as pure violates the trait contract. See ek9 -h E05150 for details.

Explanation: The Labelable trait provides a pure default for label. Overriding it without as pure would violate the purity contract inherited from the trait. See ek9 -h E05150 for details.


Question: What does 'override' mean in EK9 and when is it required?

How do I override a method in EK9?
When must I use the override keyword?
What is the override keyword in EK9?

Answer:
The 'override' keyword is required whenever a child class replaces a method from a parent class or trait. EK9 requires explicit override declarations to prevent accidental shadowing.

WHEN OVERRIDE IS REQUIRED
Whenever a method in a child class has the same name and signature as a method in a parent class or implemented trait, you must use 'override'. Omitting it produces E05110.

SYNTAX
Place 'override' before the method name:
  override process()
    -> text as String
    <- rtn as String: text.upperCase()

WHY EXPLICIT OVERRIDE
Prevents accidental shadowing: if you accidentally match a parent method, the compiler catches it. Documents intent: readers know this method replaces parent behavior. Catches renames: if the parent renames a method, the override declaration breaks, alerting you.

OVERRIDE WITH ABSTRACT
Implementing an abstract method also requires 'override':
  override calculate()
    <- rtn as Integer: 42

OVERRIDE WITH TRAITS
Implementing or replacing a trait method requires 'override':
  MyClass with trait of Printable
    override display()
      <- rtn as String: name

See Q571 for missing override keyword errors. See Q572 for false override claims. See Q573 for access modifier rules. See Q577 for operator override. See Q606 for method shadowing prevention.
See Q676 for override access hierarchy. See Q699 for return type covariance.

defines module qa.override.basics

  defines class

    Vehicle as open
      speed() as pure
        <- rtn as String: "unknown speed"

      describe() as pure
        <- rtn as String: "a vehicle"

      default operator ?

    Car extends Vehicle as open
      //Override is required: same name and signature as parent
      override speed() as pure
        <- rtn as String: "fast"

      override describe() as pure
        <- rtn as String: "a car"

      default operator ?

    ElectricCar extends Car
      override speed() as pure
        <- rtn as String: "very fast"

      override describe() as pure
        <- rtn as String: "an electric car"

      default operator ?

  defines program

    OverrideBasicsDemo()
      stdout <- Stdout()

      vehicle <- Vehicle()
      stdout.println(`Vehicle: ${vehicle.describe()}, ${vehicle.speed()}`)

      car <- Car()
      stdout.println(`Car: ${car.describe()}, ${car.speed()}`)

      electric <- ElectricCar()
      stdout.println(`Electric: ${electric.describe()}, ${electric.speed()}`)

      //Polymorphic use
      vehicles <- List() of Vehicle
      vehicles += Vehicle()
      vehicles += Car()
      vehicles += ElectricCar()

      for item in vehicles
        stdout.println(`${item.describe()} goes ${item.speed()}`)

Java: @Override annotation is optional but recommended. Python: no override concept. Rust: no method overriding (trait default implementations). Go: no inheritance. Kotlin: 'override' keyword is required (same as EK9). Swift: 'override' keyword required. C#: 'override' keyword required for virtual methods. EK9: 'override' keyword is mandatory, E05110 if missing.

E05110 abstract child explicit inherit method open override parent require virtual

Explanation: When a child class has a method matching a parent method, the override keyword is required. Omitting it causes method shadowing detection. See ek9 -h E05120 for details.

Explanation: Renaming the variable means later references to 'vehicle' become unresolved, triggering E50001. See ek9 -h E50001 for details.


Question: What happens if I forget the 'override' keyword?

Why does the compiler reject my method that matches the parent?

Answer:
If your method has the same name and signature as a parent method but you omit the 'override' keyword, the compiler reports E05110. This prevents accidental method shadowing.

THE ERROR: E05110
E05110 fires when a method matches a parent method exactly but lacks the 'override' keyword. The compiler refuses to silently shadow the parent behavior.

FIX: ADD OVERRIDE
Simply add 'override' before the method name:
  //Before (error):
  process()
    <- rtn as String: "child"
  //After (correct):
  override process()
    <- rtn as String: "child"

ALTERNATIVE: RENAME
If you intended a new method (not an override), rename it to avoid matching the parent signature.

WHY EXPLICIT OVERRIDE MATTERS
In large codebases, methods can accidentally match parent signatures after refactoring. Without mandatory override, this creates subtle bugs where the child silently replaces parent behavior. EK9 catches this at compile time.

See Q570 for override basics. See Q572 for the opposite: claiming override when nothing matches. See Q105 for method dispatch.

defines module qa.override.missingkeyword

  defines class

    Greeter as open
      greet() as pure
        -> recipient as String
        <- rtn as String: "Hello, " + recipient

      farewell() as pure
        -> recipient as String
        <- rtn as String: "Goodbye, " + recipient

      default operator ?

    //Correct: override keyword is present
    FormalGreeter extends Greeter
      override greet() as pure
        -> recipient as String
        <- rtn as String: "Good day, " + recipient

      override farewell() as pure
        -> recipient as String
        <- rtn as String: "Farewell, " + recipient

      //New method: no override needed (different name)
      bow() as pure
        -> recipient as String
        <- rtn as String: "Bowing to " + recipient

      default operator ?

  defines program

    MissingOverrideDemo()
      stdout <- Stdout()

      recipientName <- "Alice"
      greeter <- FormalGreeter()
      stdout.println(greeter.greet(recipientName))
      stdout.println(greeter.farewell(recipientName))
      stdout.println(greeter.bow(recipientName))

Java: @Override is optional, shadowing is silent without it. Python: no override mechanism. Kotlin: override is mandatory (same as EK9). C#: override keyword required for virtual methods. EK9: override is mandatory, E05110 reported if missing.

E05110 abstract explicit inherit keyword match missing open override parent shadow virtual

Explanation: When a method matches a parent method signature, omitting override causes a shadowing error. The compiler requires explicit intent. See ek9 -h E05120 for details.

Explanation: Claiming override on bow() would be a false claim since no parent method named bow exists. The override keyword must match an actual parent method. See ek9 -h E02030 for details.


Question: What happens if I claim 'override' but no parent method matches?

Why does the compiler say my override has nothing to override?

Answer:
Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure).

If you mark a method with 'override' but no parent class or trait has a matching method, the compiler reports E05100. This catches typos and stale overrides.

THE ERROR: E05100
E05100 fires when 'override' is used but no matching method exists in any parent. Common causes: method name typo, parent method was renamed or removed, wrong parent class.

FIX: REMOVE OVERRIDE
If this is a new method, remove the 'override' keyword:
  //Before (error):
  override newMethod()
    <- rtn as String: "new"
  //After (correct):
  newMethod()
    <- rtn as String: "new"

FIX: CORRECT THE NAME
If you intended to override, fix the method name to match the parent:
  //Before (error, typo):
  override procss()
  //After (correct):
  override process()

FIX: CHECK SIGNATURES
The override must match the parent signature exactly. Different parameter types mean no match.

SAFETY NET
E05100 is a safety net against stale code. When a parent method is renamed, all child overrides immediately break with E05100, forcing you to update them.

See Q570 for override basics. See Q571 for the opposite: missing override keyword. See Q573 for access modifier rules.

defines module qa.override.falseclaim

  defines class

    Renderer as open
      render() as pure
        -> content as String
        <- rtn as String: content

      default operator ?

    //Correct: override matches parent method exactly
    HtmlRenderer extends Renderer
      override render() as pure
        -> content as String
        <- rtn as String: `<p>${content}</p>`

      //New method: no override keyword (no parent match)
      renderWithTag() as pure
        ->
          content as String
          tag as String
        <- rtn as String: `<${tag}>${content}</${tag}>`

      default operator ?

  defines program

    FalseOverrideClaimDemo()
      stdout <- Stdout()

      renderer <- HtmlRenderer()
      stdout.println(renderer.render("hello"))
      stdout.println(renderer.renderWithTag("hello", "div"))

Java: @Override produces compile error if nothing to override. Kotlin: override keyword causes error if nothing matches. C#: override without matching virtual method is an error. EK9: E05100 when override has no matching parent method.

E05100 abstract claim false function inappropriate inherit mismatch open override parent typo virtual

Explanation: Adding override to renderWithTag would be a false claim since Renderer has no method named renderWithTag. The compiler rejects override when no parent method matches. See ek9 -h E05110 for details.

Explanation: Omitting override on the render method in HtmlRenderer would be detected as method shadowing since the parent Renderer already defines render. See ek9 -h E05120 for details.