Question: How does EK9 DI compare to Quarkus build-time injection?

Is EK9 DI similar to Quarkus compile-time DI?

How does Quarkus ArC compare to EK9 dependency injection?

What are the differences between Quarkus and EK9 DI?

Answer:
Quarkus and EK9 share the philosophy of build-time DI validation, but EK9 goes further with complete compile-time guarantees and simpler semantics.

QUARKUS ARC (BUILD-TIME CDI)
Quarkus uses ArC, a build-time CDI implementation that resolves beans during the build rather than at runtime. This eliminates reflection-based injection and improves startup time.

SIMILARITIES WITH EK9
1. Both validate injection during the build, not at runtime
2. Both reject circular dependencies before execution
3. Both aim for zero-overhead injection at runtime
4. Both improve startup time over Spring

KEY DIFFERENCES
1. SCOPE: Quarkus supports @ApplicationScoped, @RequestScoped, @Dependent. EK9 has singleton only. Simpler model, fewer scope-mismatch bugs.
2. CDI COMPATIBILITY: Quarkus implements CDI spec (partially). EK9 has its own injection model with no legacy compatibility burden.
3. CONDITIONAL BEANS: Quarkus supports @IfBuildProfile and build-time conditions. EK9 has no conditional registration.
4. INTERCEPTION: Quarkus supports @Interceptor and @AroundInvoke. EK9 uses aspects (a different but equally powerful mechanism).
5. COMPLETENESS: Quarkus validates what it can at build time but some CDI features still require runtime checks. EK9 validates everything at compile time.

WHY EK9 IS SIMPLER
Quarkus carries CDI legacy: qualifiers, alternatives, stereotypes, decorators, events. EK9 has: components, applications, injection fields ('!'). The entire DI model fits in one page.

See Q227 for compile-time validation. See Q234 for lifecycle. See Q327 for Spring Boot comparison.

defines module qa.di.quarkus.comparison

  defines component

    ConfigProvider as abstract
      getValue() as abstract
        -> key as String
        <- setting as String?

      default operator ?

    DefaultConfig is ConfigProvider
      override getValue()
        -> key as String
        <- setting as String: "config:" + key

      default operator ?

    HealthChecker as abstract
      check() as abstract
        <- status as String?

      default operator ?

    AppHealthChecker is HealthChecker
      config as ConfigProvider!

      override check()
        <- status <- String()
        appName <- config.getValue("app.name")
        status: "Healthy: " + appName

      default operator ?

  defines application

    BuildTimeApp
      register DefaultConfig() as ConfigProvider
      register AppHealthChecker() as HealthChecker

  defines program

    QuarkusComparisonDemo() with application of BuildTimeApp
      stdout <- Stdout()

      // === COMPILE-TIME DI LIKE QUARKUS BUT SIMPLER ===

      health as HealthChecker!

      result <- health.check()
      stdout.println(result)

      stdout.println("Build-time DI with simpler model than Quarkus CDI")

Quarkus ArC: build-time CDI, @ApplicationScoped/@RequestScoped/@Dependent scopes, @Inject annotation, partial CDI spec compliance, build-time bean discovery. Micronaut: compile-time DI via annotation processing, similar to Quarkus but non-CDI. Spring: runtime DI via reflection. EK9: compile-time DI with '!' suffix, singleton scope only, explicit application registration, complete compile-time validation with zero runtime DI failures.

arc build cdi compile dependency inject migration overhead quarkus scope startup zero

Explanation: Unlike Quarkus CDI which can inject concrete types, EK9 requires injection fields to use abstract component types. This enforces the contract-based design pattern. See ek9 -h E08150 for details.

register AppHealthChecker() as HealthChecker
      register DefaultConfig() as ConfigProvider

Explanation: AppHealthChecker injects ConfigProvider, so ConfigProvider must be registered first. EK9 validates registration order at compile time, unlike Quarkus which resolves at build time with more flexible ordering. See ek9 -h E08200 for details.


Question: How does EK9 DI compare to Google Guice modules?

What replaces Guice modules and bindings in EK9?

How is EK9 injection different from Google Guice?

Does EK9 have something like Guice's AbstractModule?

Answer:
Google Guice uses Module classes with bind() statements to configure injection. EK9's 'defines application' with 'register' statements serves the same purpose but with compile-time validation.

GUICE MODULES VS EK9 APPLICATIONS
Guice:
  class AppModule extends AbstractModule {
    @Override void configure() {
      bind(Service.class).to(ServiceImpl.class);
    }
  }
EK9:
  defines application
    MyApp
      register ServiceImpl() as Service

KEY DIFFERENCES
1. VALIDATION TIMING: Guice validates at Injector creation (runtime). EK9 validates at compilation.
2. JUST-IN-TIME BINDINGS: Guice can create instances without explicit bindings (JIT). EK9 requires explicit registration for every injection point.
3. LINKED BINDINGS: Guice chains bind(A).to(B).to(C). EK9 registers concrete against abstract directly.
4. SCOPES: Guice has @Singleton, @RequestScoped, custom scopes. EK9 has singleton only.
5. PROVIDERS: Guice has Provider<T> for lazy/deferred creation. EK9 creates all components eagerly in prepare phase.

WHY NO JIT BINDINGS
Guice's JIT bindings mean a class with @Inject constructor can be injected without any bind() call. This is convenient but dangerous: it hides dependencies and makes the object graph unpredictable. EK9 requires every injection point to have an explicit registration.

INSTALLING MODULES
Guice composes modules: install(new DatabaseModule()). EK9 does not compose applications — each application is self-contained. If you need shared registrations, define shared components and register them in each application.

See Q227 for compile-time validation. See Q228 for registration ordering. See Q324 for injection syntax. See Q330 for scope comparison.

defines module qa.di.guice.comparison

  defines component

    PaymentGateway as abstract
      charge() as abstract
        -> amount as String
        <- receipt as String?

      default operator ?

    StripeGateway is PaymentGateway
      override charge()
        -> amount as String
        <- receipt as String: "Stripe charged: " + amount

      default operator ?

    OrderProcessor as abstract
      process() as abstract
        -> orderId as String
        <- confirmation as String?

      default operator ?

    DefaultOrderProcessor is OrderProcessor
      gateway as PaymentGateway!

      override process()
        -> orderId as String
        <- confirmation <- String()
        confirmation: gateway.charge("100.00 for " + orderId)

      default operator ?

  defines application

    <?-
      This replaces Guice's AbstractModule.configure().
      register replaces bind().to().
    -?>
    GuiceEquivalentApp
      register StripeGateway() as PaymentGateway
      register DefaultOrderProcessor() as OrderProcessor

  defines program

    GuiceComparisonDemo() with application of GuiceEquivalentApp
      stdout <- Stdout()

      // === EXPLICIT REGISTRATION REPLACES bind().to() ===

      processor as OrderProcessor!

      result <- processor.process("ORD-001")
      stdout.println(result)

      stdout.println("Application registration replaces Guice modules")

Google Guice: AbstractModule.configure() with bind().to() statements, @Provides methods, just-in-time bindings, Injector creation validates at runtime, Multibinder for collections, @Singleton scope. Dagger: compile-time DI for Android, @Component/@Module annotations. EK9: 'defines application' with 'register' statements, compile-time validation, no JIT bindings, explicit registration required, singleton scope only.

bind dagger dependency explicit google guice inject injector jit migration module provider

Explanation: Unlike Guice which supports JIT bindings to concrete types, EK9 requires injection fields to use abstract component types. Explicit registration maps concrete to abstract. See ek9 -h E08150 for details.

Explanation: Removing the PaymentGateway registration leaves DefaultOrderProcessor's injection field unsatisfied. Unlike Guice JIT bindings, EK9 requires every injection point to have an explicit registration. See ek9 -h E08210 for details.


Question: Does EK9 support DI scopes like singleton, prototype, and request?

Can I create request-scoped or prototype-scoped beans in EK9?

How do I handle different component lifetimes in EK9?

Answer:
EK9 has exactly one DI scope: singleton for the program lifetime. There are no prototype, request, session, or application scopes. This is a deliberate design decision.

WHY SINGLETON ONLY
1. SCOPE MISMATCH BUGS: In Spring, injecting a request-scoped bean into a singleton silently breaks. This is one of the most common and hardest-to-diagnose DI bugs. EK9 eliminates this entire bug category.
2. SIMPLICITY: One scope means one mental model. No scoped proxies, no scope resolution strategies, no lifecycle callbacks per scope.
3. PREDICTABILITY: Every injection point references the same instance throughout program execution.

WHAT ABOUT PROTOTYPE SCOPE?
Spring's prototype scope creates a new instance per injection point. In EK9, if you need fresh instances, create them explicitly in your code. This makes object creation visible rather than hidden behind framework magic.

WHAT ABOUT REQUEST SCOPE?
For web services, EK9 creates request-specific state explicitly within the service handler. The service component is a singleton, but it creates per-request state using local variables and function calls.

HOW TO HANDLE DIFFERENT LIFETIMES
Use explicit construction for short-lived objects:
  service.handle(request)
    localState <- RequestState(request)
    processWithState(localState)
The component is a singleton; the request state is a local variable. No scope annotation needed.

See Q234 for component lifecycle. See Q117 for singleton pattern. See Q227 for compile-time validation. See Q329 for Guice scope comparison.

defines module qa.di.scopes

  defines component

    RequestHandler as abstract
      handle() as abstract
        -> path as String
        <- response as String?

      default operator ?

    <?-
      The handler is a singleton component.
      Per-request state is created as local variables.
    -?>
    WebHandler is RequestHandler

      override handle()
        -> path as String
        <- response <- String()

        // Per-request state created locally, not scope-managed
        requestId <- "REQ-" + path
        timestamp <- "2026-02-26"

        response: `${requestId} at ${timestamp}`

      default operator ?

  defines application

    SingletonApp
      register WebHandler() as RequestHandler

  defines program

    ScopeDemo() with application of SingletonApp
      stdout <- Stdout()

      // === SINGLETON COMPONENT, LOCAL STATE PER REQUEST ===

      handler as RequestHandler!

      // Simulate multiple requests to same singleton handler
      response1 <- handler.handle("/users")
      stdout.println(response1)

      response2 <- handler.handle("/orders")
      stdout.println(response2)

      stdout.println("Singleton component handles all requests")
      stdout.println("Per-request state uses local variables")

Java Spring: singleton (default), prototype, request, session, application, websocket scopes. Scope mismatch is a common bug source. Guice: unscoped, @Singleton, @RequestScoped, custom scopes via Scope interface. .NET: transient, scoped, singleton. Quarkus: @ApplicationScoped, @RequestScoped, @Dependent. EK9: singleton only, explicit construction for short-lived objects, no scope mismatch bugs possible, local variables for per-request state.

dependency inject lifetime migrate mismatch prototype proxy request scope session simplicity singleton transient

Explanation: Injection fields must use abstract types. EK9 has singleton scope only, and the abstract type is the contract through which the singleton instance is accessed. See ek9 -h E08150 for details.

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


Question: How do I test components with injected dependencies?

How do I mock injected components in EK9?

What is the EK9 equivalent of @MockBean?

How do I unit test EK9 components with DI?

Answer:
EK9 components are tested by creating test applications with mock/stub registrations. The abstract/concrete pattern makes substitution natural.

TEST APPLICATION PATTERN
Define a test application that registers test doubles instead of production components:
  defines application
    TestApp
      register MockLogger() as Logger
      register ServiceUnderTest() as Service
The ServiceUnderTest receives MockLogger through its injection field.

WHY NO @MOCKBEAN
Spring's @MockBean uses runtime reflection to replace beans in the application context. EK9 does not need this: you simply register a different concrete component against the same abstract type. The compiler validates the test wiring just as it validates production wiring.

TEST DOUBLES AS COMPONENTS
Create concrete components that implement the abstract contract with test behaviour:
  StubRepository is Repository
    override find()
      -> key as String
      <- result as String: "stub:" + key
This stub is a real component with the same contract as the production implementation.

ADVANTAGES OVER SPRING TESTING
1. NO CONTEXT LOADING: Spring tests often need @SpringBootTest which loads the entire context. EK9 test applications are lightweight.
2. COMPILE-TIME VALIDATION: Even test wiring is validated at compile time.
3. NO REFLECTION: Test doubles are concrete types, not proxy-based mocks.
4. EXPLICIT SUBSTITUTION: You can see exactly what's replaced in the test application.

See Q155 for writing unit tests. See Q227 for compile-time validation. See Q324 for injection basics. See Q234 for component lifecycle.

defines module qa.di.testing.injected

  defines component

    <?-
      Abstract contract for the dependency.
    -?>
    EmailSender as abstract
      send() as abstract
        -> message as String
        <- status as String?

      default operator ?

    <?-
      Production implementation.
    -?>
    SmtpEmailSender is EmailSender
      override send()
        -> message as String
        <- status as String: "SMTP sent: " + message

      default operator ?

    <?-
      Test double: captures sent messages for verification.
    -?>
    StubEmailSender is EmailSender
      override send()
        -> message as String
        <- status as String: "STUB captured: " + message

      default operator ?

    NotificationService as abstract
      notifyUser() as abstract
        -> userId as String
        <- result as String?

      default operator ?

    DefaultNotificationService is NotificationService
      sender as EmailSender!

      override notifyUser()
        -> userId as String
        <- result <- String()
        result: sender.send("Notification for " + userId)

      default operator ?

  defines application

    <?-
      Production application: uses real email sender.
    -?>
    ProductionApp
      register SmtpEmailSender() as EmailSender
      register DefaultNotificationService() as NotificationService

    <?-
      Test application: substitutes stub email sender.
    -?>
    TestApp
      register StubEmailSender() as EmailSender
      register DefaultNotificationService() as NotificationService

  defines program

    ProductionDemo() with application of ProductionApp
      stdout <- Stdout()

      service as NotificationService!
      result <- service.notifyUser("user-42")
      stdout.println(result)

    TestDemo() with application of TestApp
      stdout <- Stdout()

      // === TEST APPLICATION SUBSTITUTES STUB FOR PRODUCTION ===

      service as NotificationService!
      result <- service.notifyUser("user-42")
      stdout.println(result)

      stdout.println("Test double injected via test application")

Java Spring: @MockBean/@SpyBean for test doubles, @SpringBootTest for integration tests, Mockito for mocking. Guice: override bindings in test modules. .NET: replace services in IServiceCollection for tests. Python: unittest.mock, dependency_injector overrides. Go: interface-based testing, manual mock construction. EK9: test applications with substitute component registrations, compile-time validated test wiring, no reflection-based mocking.

application component double inject migrate mock stub substitute test unit verify

Explanation: Injection fields must use abstract types. This is what makes test substitution possible: both ProductionApp and TestApp register different concrete types against the same abstract EmailSender. See ek9 -h E08150 for details.

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


Question: What is the runtime overhead of EK9 DI compared to frameworks?

Does EK9 dependency injection add performance overhead?

How does EK9 DI performance compare to other languages?

Answer:
EK9 DI has near-zero runtime overhead because all validation happens at compile time and injection is simple field assignment.

SPRING DI OVERHEAD
1. REFLECTION: Spring uses reflection to discover and inject beans. Reflection is 10-100x slower than direct field access.
2. PROXY GENERATION: @Transactional, @Cacheable, and other annotations generate CGLIB proxies that add method call overhead.
3. CONTEXT STARTUP: Spring ApplicationContext initialization can take seconds to minutes for large applications.
4. CLASSPATH SCANNING: Component scanning examines every class on the classpath.

QUARKUS/MICRONAUT OVERHEAD
Both reduce startup time by moving work to build time, but still have more runtime overhead than EK9 due to CDI spec compliance and scope management.

EK9 DI OVERHEAD
1. COMPILE-TIME VALIDATION: All validation happens during compilation, zero cost at runtime.
2. SIMPLE FIELD ASSIGNMENT: Injection is direct field assignment during the prepare phase, equivalent to constructor assignment.
3. NO REFLECTION: No runtime reflection, no proxy generation.
4. NO SCOPE MANAGEMENT: Singleton-only means no scope resolution at each injection point.
5. STARTUP: Component creation is sequential field assignment, not graph resolution.

PERFORMANCE COMPARISON
Spring: seconds to start, reflection overhead per injection, proxy overhead per annotated method call.
Quarkus: sub-second start, reduced reflection, some proxy overhead.
EK9: near-instant start, zero reflection, zero proxy overhead, direct field access.

See Q227 for compile-time validation. See Q234 for lifecycle. See Q328 for Quarkus comparison.

defines module qa.di.overhead

  defines component

    Calculator as abstract
      compute() as abstract
        -> input as String
        <- output as String?

      default operator ?

    FastCalculator is Calculator
      override compute()
        -> input as String
        <- output as String: "computed:" + input

      default operator ?

    Aggregator as abstract
      aggregate() as abstract
        -> items as String
        <- total as String?

      default operator ?

    SimpleAggregator is Aggregator
      calc as Calculator!

      override aggregate()
        -> items as String
        <- total <- String()
        total: calc.compute(items)

      default operator ?

  defines application

    ZeroOverheadApp
      register FastCalculator() as Calculator
      register SimpleAggregator() as Aggregator

  defines program

    OverheadDemo() with application of ZeroOverheadApp
      stdout <- Stdout()

      // === ZERO RUNTIME OVERHEAD: direct field access ===

      aggregator as Aggregator!

      result <- aggregator.aggregate("1,2,3")
      stdout.println(result)

      stdout.println("No reflection, no proxies, no scope resolution")

Java Spring: reflection-based injection, CGLIB proxies, seconds-to-minutes startup, BeanPostProcessor overhead. Quarkus: build-time resolution, sub-second startup, reduced but present proxy overhead. Micronaut: compile-time DI, minimal reflection. Go: no DI overhead (manual wiring). Rust: no DI overhead (manual construction). EK9: compile-time validation, simple field assignment, zero reflection, zero proxy, near-instant startup.

compile cost dependency inject migrate overhead performance proxy reflection runtime speed startup zero

Explanation: Injection fields must use abstract types. Direct field assignment during the prepare phase resolves abstract types to concrete implementations with zero reflection overhead. See ek9 -h E08150 for details.

register SimpleAggregator() as Aggregator
      register FastCalculator() as Calculator

Explanation: SimpleAggregator injects Calculator, so Calculator must be registered first. Registration order determines creation order in the prepare phase. See ek9 -h E08200 for details.


Question: How do I manage transactions with try-with-resources in EK9?

How do I use the Transaction trait with try-with-resources?

What is the simplest transaction pattern in EK9?

How does operator close work with transactions?

Answer:
The simplest and most recommended transaction pattern uses try-with-resources with a type implementing the Transaction trait. Transaction extends Closeable, so operator close provides automatic cleanup.

BASIC PATTERN
Create a transaction in the try header, use it in the body, commit on success:
  try
    -> txn <- DatabaseTransaction(connection)
    updateRecords(txn)
    txn.commit()
  catch
    -> ex as Exception
    handleError(ex)
When the try scope exits, operator close is called automatically.

AUTO-ROLLBACK SAFETY NET
The operator close implementation can check isCommitted() and rollback if the transaction was not committed. This prevents partial commits when exceptions occur between operations.

WHY THIS PATTERN IS BEST
1. TRANSACTION SCOPE IS VISIBLE: the try block clearly delimits transaction boundaries
2. AUTOMATIC CLEANUP: operator close is called regardless of how the scope exits
3. EXCEPTION SAFE: exceptions trigger automatic cleanup, no finally block needed
4. AI FRIENDLY: AI can see the transaction scope and generate correct code
5. SIMILAR TO RUST: mirrors Diesel's RAII pattern and Go's defer

COMPARISON WITH SPRING
Spring @Transactional hides the transaction boundary. Self-invocation bypasses the proxy. Checked exceptions silently commit. EK9's try-with-resources has none of these failure modes.

See Q137 for try-with-resources basics. See Q134 for try/catch patterns. See Q334 for delegation pattern. See Q335 for callback pattern. See Q338 for partial commit prevention.

defines module qa.di.transaction.try.resources

  defines trait

    <?-
      Connection abstraction for the example.
    -?>
    Connection
      execute()
        -> statement as String
        <- result as String?

  defines class

    <?-
      A concrete transaction that tracks commit state.
      Implements Transaction trait (which extends Closeable).
    -?>
    DatabaseTransaction with trait of Transaction
      connectionName <- String()
      committed <- false

      DatabaseTransaction()
        -> connectionName as String
        this.connectionName: connectionName

      executeUpdate()
        -> statement as String
        <- result as String: `${connectionName}: ${statement}`

      override commit()
        committed: true

      override rollback()
        committed: false

      override isCommitted() as pure
        <- rtn as Boolean: committed

      override operator close as pure
        stdout <- Stdout()
        stdout.println("Close: committed=" + $committed)

      override operator ? as pure
        <- rtn <- true

  defines program

    TransactionTryResourcesDemo()
      stdout <- Stdout()

      // === TRY-WITH-RESOURCES: most explicit pattern ===

      stdout.println("=== Successful transaction ===")
      try
        -> txn <- DatabaseTransaction("primary-db")
        result1 <- txn.executeUpdate("UPDATE accounts SET balance = 100")
        stdout.println(result1)
        result2 <- txn.executeUpdate("INSERT INTO audit_log VALUES ('transfer')")
        stdout.println(result2)
        txn.commit()
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

      // === UNCOMMITTED TRANSACTION: close called without commit ===

      stdout.println("=== Uncommitted transaction ===")
      try
        -> txn <- DatabaseTransaction("secondary-db")
        result3 <- txn.executeUpdate("UPDATE inventory SET count = 0")
        stdout.println(result3)
        // No commit — operator close sees committed=false
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

Java Spring: @Transactional annotation with proxy-based AOP, seven silent failure modes. Go: defer tx.Rollback() after tx, err := db.Begin(). Rust: conn.transaction(|txn| { ... }) RAII closure. C#: using (var scope = new TransactionScope()) { ... scope.Complete(); }. Python: with conn.begin() as txn: .... EK9: try -> txn <- Transaction() with operator close for auto-cleanup, isCommitted() check in close for safety.

automatic cleanup close commit dependency inject migrate resources rollback safety scope transaction try

Explanation: Class fields must be initialized inline at declaration. Declaring a field without initialization produces a field-not-initialized error unless the field is an injection point ('!'). See ek9 -h E08180 for details.


Question: How do I add transactions with trait delegation in EK9?

How do I use the decorator pattern for transactions in EK9?

Can I wrap a service with transaction management in EK9?

What is trait delegation for transaction handling?

Answer:
EK9's 'by delegate' syntax wraps a service with transaction management without modifying the original service. This is the Decorator pattern applied to transactions.

DELEGATION PATTERN
Define a trait for the service contract, then create a delegating wrapper:
  TransactionalService with trait of Service by delegate
    delegate as Service: BasicService()
    override process()
      -> input as String
      <- result <- String()
      // wrap with transaction logic
      result: delegate.process(input)

HOW IT WORKS
1. 'by delegate' tells the compiler to generate delegation methods for all abstract methods
2. You override only the methods that need transaction wrapping
3. Non-overridden methods pass through directly to the delegate
4. The original service is unaware of transaction management

ADVANTAGES
1. SEPARATION OF CONCERNS: business logic and transaction management are separate
2. COMPOSABLE: multiple decorators can be stacked (logging, caching, transactions)
3. TESTABLE: test the service without transactions, test the wrapper separately
4. OPEN/CLOSED: add transactions without modifying existing service code

SIMILAR TO C# MIDDLEWARE
This pattern mirrors C#'s middleware pipeline where each layer wraps the next. But in EK9, the delegation is compile-time generated and type-safe.

See Q210 for trait delegation basics. See Q333 for try-with-resources pattern. See Q266 for cross-cutting concerns. See Q335 for callback pattern.

defines module qa.di.transaction.decorator

  defines trait

    <?-
      Service contract as a trait (required for delegation).
    -?>
    OrderService
      processOrder() as abstract
        -> orderId as String
        <- result as String?

      cancelOrder() as abstract
        -> orderId as String
        <- result as String?

  defines class

    <?-
      Concrete service with business logic only.
      No transaction awareness.
    -?>
    BasicOrderService with trait of OrderService
      override processOrder()
        -> orderId as String
        <- result as String: "Processed: " + orderId

      override cancelOrder()
        -> orderId as String
        <- result as String: "Cancelled: " + orderId

      default operator ?

    <?-
      Transactional wrapper using delegation.
      Only processOrder gets transaction wrapping.
      cancelOrder passes through to delegate unchanged.
    -?>
    TransactionalOrderService with trait of OrderService by delegate
      delegate as OrderService: BasicOrderService()

      TransactionalOrderService()
        -> service as OrderService
        this.delegate: service

      override processOrder()
        -> orderId as String
        <- result <- String()
        stdout <- Stdout()
        stdout.println("BEGIN transaction for " + orderId)
        result: delegate.processOrder(orderId)
        stdout.println("COMMIT transaction for " + orderId)

      default operator ?

  defines program

    TransactionDecoratorDemo()
      stdout <- Stdout()

      // === DELEGATION: wrap service with transaction ===

      basicService <- BasicOrderService()
      transactionalService <- TransactionalOrderService(basicService)

      // processOrder is wrapped with transaction
      result1 <- transactionalService.processOrder("ORD-001")
      stdout.println(result1)

      // cancelOrder passes through unchanged via delegation
      result2 <- transactionalService.cancelOrder("ORD-002")
      stdout.println(result2)

      stdout.println("Delegation adds transactions without modifying service")

Java Spring: @Transactional on service methods, proxy-based decoration. C#: middleware pipeline, HttpClient DelegatingHandler. Go: middleware functions wrapping handlers. Rust: tower middleware layers. Python: decorators (@transactional). EK9: 'by delegate' for compile-time generated delegation, override specific methods for transaction wrapping, type-safe composition.

abstract compose decorator delegate dependency inject layer middleware migrate open override pattern separation service trait virtual wrap

Explanation: Class fields must be initialized at declaration. The delegate field needs a default value. Only injection fields ('!') can be left uninitialized because the DI framework provides their values. See ek9 -h E08180 for details.


Question: How do I use higher-order functions for transaction management?

Can I pass business logic to a transaction wrapper function?

What is the callback pattern for transactions in EK9?

How do I create a reusable transaction execution function?

Answer:
Pass business logic as a function to a transaction wrapper. This creates a reusable transaction execution framework inspired by Rust/Diesel's closure pattern.

CALLBACK PATTERN
Define a function that accepts a business operation and wraps it in a transaction:
  executeInTransaction()
    -> operation as BusinessOperation
    <- result as String?
    try
      -> txn <- Transaction()
      result: operation("context")
      txn.commit()
    catch
      -> ex as Exception
      result: "Failed"

HOW IT WORKS
1. The transaction wrapper creates and manages the transaction
2. Business logic is passed as a function (Consumer, Function, etc.)
3. The wrapper calls the business logic within the transaction scope
4. Commit happens after successful execution, rollback on exception

ADVANTAGES
1. REUSABLE: one transaction wrapper serves all business operations
2. CONSISTENT: transaction management logic is centralized
3. TESTABLE: test business logic independently of transaction management
4. EXPLICIT: transaction scope is visible at the call site

SIMILAR TO RUST DIESEL
Rust's conn.transaction(|txn| { ... }) pattern. EK9 achieves the same with higher-order functions.

See Q56 for higher-order functions. See Q52 for dynamic functions. See Q333 for try-with-resources pattern. See Q336 for closure capture pattern.

defines module qa.di.transaction.callback

  defines class

    SimpleTransaction with trait of Transaction
      label <- String()
      committed <- false

      SimpleTransaction()
        -> label as String
        this.label: label

      override commit()
        committed: true

      override rollback()
        committed: false

      override isCommitted() as pure
        <- rtn as Boolean: committed

      override operator close as pure
        stdout <- Stdout()
        if committed
          stdout.println($label + ": committed on close")
        else
          stdout.println($label + ": rolled back on close")

      override operator ? as pure
        <- rtn <- true

  defines function

    <?-
      Business operation type: accepts a label and returns a result.
    -?>
    BusinessOperation() as abstract
      -> context as String
      <- result as String?

    <?-
      Concrete operation: create user.
    -?>
    CreateUserOp() is BusinessOperation
      -> context as String
      <- result as String: "Created user in " + context

    <?-
      Concrete operation: update inventory.
    -?>
    UpdateInventoryOp() is BusinessOperation
      -> context as String
      <- result as String: "Updated inventory in " + context

    <?-
      Reusable transaction wrapper: executes any operation in a transaction.
    -?>
    executeInTransaction()
      -> operation as BusinessOperation
      <- result <- String()
      try
        -> txn <- SimpleTransaction("auto-txn")
        result: operation("within-transaction")
        txn.commit()
      catch
        -> ex as Exception
        result: "Transaction failed: " + $ex

  defines program

    TransactionCallbackDemo()
      stdout <- Stdout()

      // === CALLBACK PATTERN: pass business logic to wrapper ===

      result1 <- executeInTransaction(CreateUserOp)
      stdout.println(result1)

      result2 <- executeInTransaction(UpdateInventoryOp)
      stdout.println(result2)

      stdout.println("Reusable wrapper executes any operation in a transaction")

Rust/Diesel: conn.transaction(|txn| { ... }) closure pattern. Kotlin/Exposed: transaction { ... } block. Go: custom withTransaction(func(tx *Tx) error) pattern. Java: Spring TransactionTemplate.execute(callback). C#: custom ExecuteInTransaction(Action) pattern. EK9: higher-order functions with dynamic function callbacks, captured closure state, try-with-resources inside wrapper.

callback closure consumer dependency execute function higher inject migrate order pattern reusable wrapper

Explanation: Class fields must be initialized at declaration. Leaving a field without initialization and without the injection suffix '!' produces a field-not-initialized error. See ek9 -h E08180 for details.


Question: How do dynamic functions solve transaction API pollution?

How do I avoid passing transaction through every function?

What is the closure capture pattern for transactions?

How do dynamic functions prevent API pollution in EK9?

Answer:
Dynamic functions capture transaction state in closures, keeping downstream APIs clean. This solves Go's #1 complaint about explicit transaction management.

THE PROBLEM: API POLLUTION
In Go, if processOrder() needs a transaction, and it calls validateInventory(), which calls checkWarehouse(), ALL three functions must accept a transaction parameter. This creates massive API surface bloat.

EK9'S SOLUTION: CLOSURE CAPTURE
Create a dynamic function that captures the transaction:
  createProcessor()
    -> txn as Transaction
    <- processor as Processor
    processor: () is Processor as function ...
Downstream functions (doWork, etc.) have clean APIs with no transaction parameter.

HOW IT WORKS
1. Factory function receives the transaction context
2. Dynamic function captures the context in its closure
3. Downstream functions are called without transaction parameters
4. Transaction is managed by the factory caller, not the interior functions

WHY THIS IS TRANSFORMATIVE
1. EXPLICIT AT THE BOUNDARY: the factory clearly shows transaction capture
2. CLEAN INTERIOR: downstream APIs are unpolluted by transaction plumbing
3. TYPE SAFE: the captured state is typed, not a context value bag
4. TESTABLE: downstream functions can be tested without any transaction setup

COMBINES GO AND RUST ADVANTAGES
Go's explicitness (transaction is a real value) plus Rust's closure elegance (captured state is invisible to callers).

See Q53 for closure capture basics. See Q52 for dynamic functions. See Q333 for try-with-resources. See Q335 for callback pattern.

defines module qa.di.transaction.closure

  defines function

    <?-
      Abstract processor with clean API: no transaction parameter.
    -?>
    OrderProcessor() as abstract
      -> orderId as String
      <- result as String?

    <?-
      Downstream function: no transaction parameter needed.
    -?>
    validateOrder()
      -> orderId as String
      <- isValid as Boolean: orderId?

    <?-
      Another downstream function: clean API.
    -?>
    formatConfirmation()
      -> orderId as String
      <- confirmation as String: "Confirmed: " + orderId

    <?-
      Downstream: builds final result with captured label.
    -?>
    buildResult()
      ->
        txnLabel as String
        orderId as String
      <- result <- String()
      valid <- validateOrder(orderId)
      if valid
        confirmation <- formatConfirmation(orderId)
        result: `${txnLabel} | ${confirmation}`
      else
        result: `${txnLabel} | validation failed`

  defines program

    ClosureCaptureDemo()
      stdout <- Stdout()

      // === CLOSURE CAPTURE CONCEPT ===
      // In full EK9, a factory function would return a dynamic function
      // that captures txnLabel in its closure.
      // Here we demonstrate the same concept with explicit function calls.

      txnLabel <- "TXN-001"

      // Downstream functions have clean APIs - no transaction parameter
      result1 <- buildResult(txnLabel, "ORD-100")
      stdout.println(result1)

      result2 <- buildResult(txnLabel, "ORD-200")
      stdout.println(result2)

      stdout.println("Transaction context at boundary, clean APIs inside")

Go: context.Context + tx parameter pollution through every function in the chain. Rust: closures capture state naturally. Java: ThreadLocal for ambient state (fragile). C#: TransactionScope ambient (fragile). Python: contextvars for ambient state. EK9: dynamic functions capture transaction in closure, downstream APIs stay clean, explicit at boundary, invisible in interior.

anonymous api capture clean closure dependency dynamic enclosing factory function inject migrate pollution scope

result1XYZ <- buildResult(txnLabel, "ORD-100")

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

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


Question: Can I use aspects for transactions like Spring @Transactional?

Does EK9 support AOP-based transaction management?

How do I use aspects for cross-cutting transaction concerns?

What is the AOP approach to transactions in EK9?

Answer:
Yes, EK9 supports AOP-based transaction management through its aspect system. However, this pattern should be used with caution because it makes transaction boundaries invisible at the call site.

ASPECT-BASED TRANSACTIONS
Define a TransactionAspect that extends Aspect:
  TransactionAspect extends Aspect
    override beforeAdvice()
      -> joinPoint as JoinPoint
      <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)
    override afterAdvice()
      -> preparedMetaData as PreparedMetaData
Register with: register Service() as ServiceType with aspect of TransactionAspect()

HOW IT WORKS
1. beforeAdvice() runs before the advised method (start transaction)
2. The advised method executes within the transaction context
3. afterAdvice() runs after the advised method (commit or rollback)
4. The aspect is registered in the application definition

CAUTION: INVISIBLE BEHAVIOUR
This pattern has the same problem as Spring @Transactional: the transaction is invisible at the call site. A developer reading service.process(order) cannot see that a transaction exists. This is acceptable for truly uniform cross-cutting concerns but dangerous for business-critical transaction logic.

WHEN TO USE ASPECTS
1. ALL methods on a service need identical transaction behavior
2. Transaction logic is truly uniform (no method-specific rollback rules)
3. The team understands that transactions are managed by the aspect
4. Auditing, logging, or metrics that are purely cross-cutting

WHEN TO PREFER EXPLICIT PATTERNS
1. Different methods need different transaction strategies
2. Transaction scope doesn't align with method boundaries
3. AI is generating the code (AI cannot see aspect behavior)
4. Debugging transaction issues is a concern

See Q114 for aspect basics. See Q266 for cross-cutting concerns. See Q333 for try-with-resources (recommended). See Q334 for delegation pattern.

defines module qa.di.transaction.aspect

  defines component

    PaymentService as abstract
      processPayment() as abstract
        -> amount as String
        <- receipt as String?

      default operator ?

    BasicPaymentService is PaymentService
      override processPayment()
        -> amount as String
        <- receipt as String: "Payment processed: " + amount

      default operator ?

  defines class

    <?-
      Transaction aspect: wraps advised methods with transaction behavior.
    -?>
    TransactionAspect extends Aspect
      label <- String()

      TransactionAspect()
        -> label as String
        this.label: label

      override beforeAdvice()
        -> joinPoint as JoinPoint
        <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)
        Stdout().println(`${label} BEGIN: ${joinPoint.componentName()}.${joinPoint.methodName()}`)

      override afterAdvice()
        -> preparedMetaData as PreparedMetaData
        joinPoint <- preparedMetaData.joinPoint()
        Stdout().println(`${label} COMMIT: ${joinPoint.componentName()}.${joinPoint.methodName()}`)

      default operator ?

  defines application

    TransactionalAspectApp
      register BasicPaymentService() as PaymentService with aspect of TransactionAspect("TXN")

  defines program

    TransactionAspectDemo() with application of TransactionalAspectApp
      stdout <- Stdout()

      // === AOP: transaction is invisible at this call site ===

      payment as PaymentService!

      receipt <- payment.processPayment("99.95")
      stdout.println(receipt)

      stdout.println("Aspect adds transaction, but behavior is invisible here")
      stdout.println("Prefer explicit patterns for critical business logic")

Java Spring: @Transactional annotation, proxy-based AOP, seven silent failure modes. AspectJ: compile-time weaving for more reliable AOP. Go: no AOP support, explicit middleware only. Rust: no AOP support. C#: Castle DynamicProxy or middleware. EK9: Aspect class with beforeAdvice/afterAdvice, 'with aspect of' registration, compile-time wiring, but invisible behavior at call site.

advice after aop aspect before caution cross cross-cutting cutting dependency inject invisible migrate transactional weave

Explanation: Injection fields must use abstract component types. The aspect is applied through the application registration, and the program injects the abstract type. See ek9 -h E08150 for details.

Explanation: Class fields must be initialized at declaration. Without initialization and without the '!' injection suffix, the compiler reports a field-not-initialized error. See ek9 -h E08180 for details.


Question: How does EK9 prevent partial transaction commits?

How do I ensure all-or-nothing transactions in EK9?

What prevents half-committed data in EK9?

How does EK9 handle transaction atomicity?

Answer:
EK9 prevents partial commits through three structural mechanisms: operator close auto-cleanup, no early return, and the isCommitted() safety check.

MECHANISM 1: OPERATOR CLOSE AUTO-CLEANUP
Transaction extends Closeable. When used with try-with-resources, operator close is called automatically when the scope exits. Implementations check isCommitted() in close and rollback if not committed. This prevents the 'forgot to rollback' bug.

MECHANISM 2: NO EARLY RETURN
EK9 has no return statement. You cannot return early from the middle of a transaction, which is one of the most common causes of partial commits in other languages. The transaction scope always runs to completion or throws.

MECHANISM 3: isCommitted() SAFETY CHECK
The isCommitted() method returns a tri-state Boolean:
- Unset: transaction state unknown (default, not yet committed or rolled back)
- True: committed
- False: explicitly not committed (active transaction)
Operator close can check this and take appropriate action.

COMMON PARTIAL COMMIT SCENARIOS PREVENTED
1. EXCEPTION BETWEEN OPERATIONS: operator close triggers rollback automatically
2. EARLY RETURN: impossible in EK9 (no return statement)
3. FORGOT TO COMMIT: operator close detects uncommitted state
4. FORGOT TO ROLLBACK: operator close handles cleanup

SPRING SILENT COMMIT BUG
In Spring, checked exceptions silently commit the transaction. A method that throws IOException commits partial data. EK9's operator close checks commit state regardless of exception type.

See Q333 for try-with-resources pattern. See Q144 for no break/continue/return. See Q134 for exception handling. See Q137 for try-with-resources basics.

defines module qa.di.transaction.no.partial

  defines class

    SafeTransaction with trait of Transaction
      identifier <- String()
      committed <- false
      operations <- 0

      SafeTransaction()
        -> identifier as String
        this.identifier: identifier

      recordOperation()
        -> description as String
        <- status as String: `${identifier} op#${operations}: ${description}`
        operations: operations + 1

      override commit()
        committed: true

      override rollback()
        committed: false
        operations: 0

      override isCommitted() as pure
        <- rtn as Boolean: committed

      <?-
        Safety net: auto-rollback if not committed.
      -?>
      override operator close as pure
        stdout <- Stdout()
        if committed
          stdout.println(`${identifier}: close after commit (${operations} ops)`)
        else
          stdout.println(`${identifier}: ROLLBACK on close (${operations} uncommitted ops)`)

      override operator ? as pure
        <- rtn <- true

  defines program

    NoPartialCommitDemo()
      stdout <- Stdout()

      // === SUCCESSFUL: all operations committed ===

      stdout.println("=== Committed transaction ===")
      try
        -> txn <- SafeTransaction("TXN-A")
        status1 <- txn.recordOperation("debit account")
        stdout.println(status1)
        status2 <- txn.recordOperation("credit account")
        stdout.println(status2)
        txn.commit()
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

      // === UNCOMMITTED: operator close auto-rollbacks ===

      stdout.println("=== Uncommitted transaction ===")
      try
        -> txn <- SafeTransaction("TXN-B")
        status3 <- txn.recordOperation("update inventory")
        stdout.println(status3)
        status4 <- txn.recordOperation("send notification")
        stdout.println(status4)
        // No commit — operator close detects and reports
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

      stdout.println("Operator close prevents partial commits automatically")

Java Spring: checked exceptions silently commit, @Transactional(rollbackFor) needed for explicit control. Go: must remember defer tx.Rollback() after every tx.Begin(). Rust: Drop trait ensures cleanup but requires explicit commit. C#: TransactionScope.Complete() must be called explicitly. Python: context manager __exit__ handles cleanup. EK9: operator close auto-cleanup, no return prevents mid-transaction exit, isCommitted() tri-state check, structural prevention of partial commits.

atomicity cleanup close commit dependency inject migrate partial prevent return rollback safety structural

Explanation: Class fields must be initialized at declaration. Without initialization or the injection suffix '!', the compiler reports a field-not-initialized error. See ek9 -h E08180 for details.


Question: Which transaction management pattern should I use in EK9?

How do I choose between EK9 transaction patterns?

What is the best transaction pattern for my use case?

When should I use try-with-resources vs delegation vs aspects for transactions?

Answer:
EK9 offers five transaction patterns. Choose based on your specific requirements using this decision guide.

DEFAULT CHOICE: TRY-WITH-RESOURCES (Pattern 1)
Use for: most CRUD operations, single-function transaction scope
Why: most explicit, easiest to debug, AI generates correctly
Code: try -> txn <- Transaction() ... txn.commit()

CONTEXT PASSING (Pattern 2)
Use for: multiple functions need shared state beyond just the transaction
Why: typed context record carries transaction + audit log + correlation ID
When: call chains need rich context, not just the transaction

DELEGATION/DECORATOR (Pattern 3)
Use for: adding transactions to existing services without modification
Why: separation of concerns, composable, testable
When: layered architecture, multiple cross-cutting concerns

AOP ASPECT (Pattern 4)
Use for: truly uniform transaction behavior across many services
Caution: makes transactions invisible at call site
When: team understands AOP, behavior is identical for all advised methods

CALLBACK/HIGHER-ORDER (Pattern 5)
Use for: reusable transaction execution framework
Why: centralizes transaction management, any operation can use it
When: infrastructure code, many different operations need same wrapping

DYNAMIC FUNCTIONS (combine with any pattern)
Use for: preventing API pollution in deep call chains
Why: captures transaction in closure, keeps downstream APIs clean
When: transaction scope is wide but most functions do not need direct access

DECISION FLOWCHART
1. Is the transaction scope a single function? -> Pattern 1 (try-with-resources)
2. Do multiple functions need shared context? -> Pattern 2 (context record)
3. Are you wrapping existing services? -> Pattern 3 (delegation)
4. Is behavior truly uniform across all services? -> Pattern 4 (aspect, with caution)
5. Do you want a reusable framework? -> Pattern 5 (callback)
6. Is API pollution a concern? -> Add dynamic functions to any pattern

See Q333 for Pattern 1. See Q334 for Pattern 3. See Q335 for Pattern 5. See Q336 for dynamic functions. See Q337 for Pattern 4. See Q340 for AI-friendliness analysis.

defines module qa.di.transaction.selection

  defines class

    MiniTransaction with trait of Transaction
      identifier <- String()
      committed <- false

      MiniTransaction()
        -> identifier as String
        this.identifier: identifier

      override commit()
        committed: true

      override isCommitted() as pure
        <- rtn as Boolean: committed

      override operator close as pure
        stdout <- Stdout()
        stdout.println(`${identifier} closed (committed=${committed})`)

      override operator ? as pure
        <- rtn <- true

  defines program

    PatternSelectionDemo()
      stdout <- Stdout()

      // === PATTERN 1: Try-with-resources (DEFAULT CHOICE) ===

      stdout.println("=== Pattern 1: Try-with-resources ===")
      try
        -> txn <- MiniTransaction("P1")
        stdout.println("Business logic here")
        txn.commit()
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

      // Most use cases are covered by Pattern 1 above
      // Only use other patterns when Pattern 1 doesn't fit

      stdout.println("Default: try-with-resources")
      stdout.println("Context passing: when multiple functions need shared state")
      stdout.println("Delegation: when wrapping existing services")
      stdout.println("Aspect: when behavior is truly uniform (use with caution)")
      stdout.println("Callback: when building reusable transaction framework")

Spring: primarily @Transactional (AOP), TransactionTemplate (callback). Go: explicit transaction passing (context pattern). Rust: Diesel closure pattern (callback). C#: TransactionScope (ambient), middleware (delegation). Kotlin: transaction { } block (callback). EK9: five patterns from most-explicit to most-implicit, try-with-resources recommended as default, dynamic functions solve API pollution uniquely.

choose comparison decision dependency flowchart guide inject pattern recommendation selection use when

Explanation: Class fields must be initialized at declaration. The MiniTransaction class initializes all its fields inline. Omitting initialization without the '!' injection suffix is a compile-time error. See ek9 -h E08180 for details.


Question: Why are EK9 transaction patterns better for AI-generated code?

How do explicit transactions help AI code generation?

Why does AI struggle with Spring @Transactional?

What makes EK9 transaction patterns AI-friendly?

Answer:
EK9's explicit transaction patterns are dramatically better for AI code generation because AI models are pattern matchers on syntax, and explicit patterns put transaction behavior into syntax.

WHY AI STRUGGLES WITH @TRANSACTIONAL
1. INVISIBLE BEHAVIOUR: @Transactional creates behavior with no syntactic presence at the call site. AI generating code that calls a @Transactional method has no signal that a transaction exists.
2. SELF-INVOCATION TRAP: AI frequently generates code that calls @Transactional methods from within the same class, bypassing the proxy. This is the #1 Spring transaction bug.
3. EXCEPTION CONFUSION: AI cannot reliably know which exceptions trigger rollback vs commit in Spring's model.
4. CONFIGURATION DEPENDENCY: AI may generate @Transactional without @EnableTransactionManagement, producing silently broken code.

WHY EK9 PATTERNS WIN FOR AI
1. SYNTACTIC SIGNALS: try -> txn <- Transaction() puts the transaction directly in the code. AI can see it, reason about it, and generate correct interactions.
2. COMPILER FEEDBACK: When AI generates incorrect transaction code, EK9's compiler provides specific error messages (via -E3) that guide correction.
3. NO HIDDEN STATE: Every transaction pattern in EK9 has visible syntax. No proxies, no ambient state, no annotation magic.
4. PATTERN MATCHING: AI excels at reproducing patterns it can see. EK9's try-with-resources transaction pattern is a clear, reproducible template.

THE DUAL-AUDIENCE SHIFT
From 2027, code serves two audiences: humans and AI. Both need the same thing: visible behavior in syntax. Languages that hide behavior in annotations, proxies, and conventions are optimized for neither audience.

COMPILER AS AI CONTROL PROTOCOL
EK9's compiler error cascade acts as an AI steering mechanism. Each error message is a course correction signal. For transactions:
- Missing operator close implementation -> add it
- Type mismatch on Transaction -> fix the type
- Unused variable warning -> use or remove the transaction
Silence means correctness across all dimensions.

See Q281 for AI code verification. See Q323 for AI and code quality. See Q333 for try-with-resources pattern. See Q339 for pattern selection guide.

defines module qa.di.transaction.ai.friendly

  defines class

    AIFriendlyTransaction with trait of Transaction
      label <- String()
      committed <- false

      AIFriendlyTransaction()
        -> label as String
        this.label: label

      override commit()
        committed: true

      override isCommitted() as pure
        <- rtn as Boolean: committed

      override operator close as pure
        stdout <- Stdout()
        stdout.println(`CLOSE ${label} committed=${committed}`)

      override operator ? as pure
        <- rtn <- true

  defines function

    <?-
      This function demonstrates the pattern AI generates best:
      explicit try-with-resources with visible transaction scope.
    -?>
    processWithTransaction()
      -> input as String
      <- output <- String()

      try
        -> txn <- AIFriendlyTransaction("ai-txn")
        // AI can see the transaction scope clearly
        // AI can see commit() is needed before scope exits
        output: "Processed: " + input
        txn.commit()
      catch
        -> ex as Exception
        output: "Failed: " + $ex

  defines program

    AIFriendlyDemo()
      stdout <- Stdout()

      // === AI GENERATES THIS PATTERN CORRECTLY ===

      result <- processWithTransaction("order-data")
      stdout.println(result)

      // AI can see:
      // 1. Transaction created in try header
      // 2. Business logic in try body
      // 3. commit() before scope exits
      // 4. operator close handles cleanup
      // No hidden behavior, no proxy magic, no annotation confusion

      stdout.println("Explicit patterns: AI sees, AI generates correctly")
      stdout.println("Compiler catches mistakes via error cascade")

AI with Spring: generates @Transactional that silently fails (self-invocation, missing config, wrong exception handling). AI with Go: generates correct explicit code but with API pollution. AI with Rust: generates correct closure patterns. AI with EK9: generates correct try-with-resources patterns, compiler catches mistakes, error messages guide correction, explicit syntax matches AI's pattern-matching strength.

ai code compiler dependency explicit friendly generate inject invisible migrate pattern signal syntax

Explanation: Class fields must be initialized at declaration. AI code generators must ensure all fields have initial values. Only injection fields ('!') can omit initialization. See ek9 -h E08180 for details.


Question: How do I get the current date and time in EK9?

What is the difference between Date() and Date().today()?

Answer:
EK9 has three factory methods for 'now': Date().today(), Time().now(), and DateTime().now(). The critical distinction is that the no-arg constructor creates an UNSET value, not the current moment.

GETTING THE CURRENT MOMENT
Date().today() returns today's calendar date.
Time().now() returns the current wall-clock time.
DateTime().now() returns the current date, time, and timezone.
DateTime().today() is an alias for DateTime().now().

THE UNSET CONSTRUCTOR TRAP
Date() creates an unset date (not today). Time() creates an unset time (not now). DateTime() creates an unset DateTime (not now). This follows EK9's tri-state design (see Q29): every type can be present-but-unset, which is different from absent and from set-with-value.

Why? Because 'no date specified yet' is a legitimate state. Think of a form where the user has not entered their birth date. You need to distinguish 'not entered' from 'today' from 'some specific date'.

PATTERN: GUARD AGAINST UNSET
Since the factory methods return new set values, you can use guard patterns:
  if today <- Date().today()
    stdout.println(`Today is ${today}`)
This always executes because today() always returns a set value.

See Q31 for Date and Time basics. See Q92 for DateTime with timezones. See Q29 for tri-state semantics. See Q543 for Unix timestamps. See Q544 for elapsed time benchmarking.

defines module qa.current.date.time

  defines program
    CurrentDateTimeDemo()
      stdout <- Stdout()

      // Date() is UNSET - not today
      unsetDate <- Date()
      stdout.println(`Date() isSet: ${unsetDate?}`)

      // Date().today() gets the current date
      currentDate <- Date().today()
      stdout.println(`Today: ${currentDate}`)

      // Time() is UNSET - not now
      unsetTime <- Time()
      stdout.println(`Time() isSet: ${unsetTime?}`)

      // Time().now() gets the current time
      currentTime <- Time().now()
      stdout.println(`Now: ${currentTime}`)

      // DateTime() is UNSET - not now
      unsetDT <- DateTime()
      stdout.println(`DateTime() isSet: ${unsetDT?}`)

      // DateTime().now() gets the current date+time+timezone
      currentDT <- DateTime().now()
      stdout.println(`Now: ${currentDT}`)

      // DateTime().today() is an alias for now()
      todayDT <- DateTime().today()
      stdout.println(`Today: ${todayDT}`)

      // Guard pattern - always executes because today() returns set value
      if today <- Date().today()
        stdout.println(`Guard confirmed today: ${today}`)

      // All three are set
      require currentDate?
      require currentTime?
      require currentDT?

      // The unset ones are not
      require ~unsetDate?
      require ~unsetTime?
      require ~unsetDT?

Java: LocalDate.now(), LocalTime.now(), ZonedDateTime.now() static factory methods. Python: datetime.date.today(), datetime.datetime.now(). JavaScript: new Date() gives current moment (confusing: constructor IS the factory). Go: time.Now(). Rust: chrono::Local::now(). EK9: Date().today(), Time().now(), DateTime().now() factory methods; no-arg constructor creates unset, not current.

clock current date datetime duration factory now time timezone today unset

Explanation: EK9 uses factory methods on instances, not static methods. Date().today() creates an unset Date then calls today() on it. Date.now() does not exist. Triggers E50001 — method not resolved. See ek9 -h Date for the full API.

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


Question: How do I format a date for display in EK9?

How do I convert a date to a string in EK9?

What date format does the $ operator produce?

How do I display dates in different formats?

Answer:
EK9 provides two approaches: the $ operator for ISO 8601 and Locale methods for human-readable regional formatting.

ISO 8601 VIA $ OPERATOR
The $ operator on Date, Time, and DateTime always produces ISO 8601 strings:
  $birthday gives '1971-02-01'
  $meeting gives '09:30:00'
  $timestamp gives '2024-06-15T10:30:00Z'
This is the machine-readable format, suitable for APIs, logging, and data exchange.

LOCALE FORMATTING — FOUR NAMED LEVELS
For human-readable display, use Locale with four named methods:
  shortFormat: compact, numeric (03/10/2020 or 10/3/20)
  mediumFormat: abbreviated month (Oct 3, 2020)
  longFormat: full month name (3. oktobra 2020)
  fullFormat: includes day-of-week (Saturday, 3 October 2020)

Each level adapts automatically to the locale's conventions.

NO PATTERN STRINGS
EK9 deliberately avoids format pattern strings like 'yyyy-MM-dd' or 'dd/MM/yyyy'. Pattern strings are error-prone: 'mm' means minutes in Java but months in Excel, 'DD' means day-of-year in Java but day-of-month in Moment.js. Named levels eliminate this entire class of bugs.

See Q44 for complete Locale formatting guide. See Q549 for locale date formatting examples. See Q551 for RFC 7231 HTTP date headers.

defines module qa.format.date.display

  defines program
    FormatDateDisplayDemo()
      stdout <- Stdout()

      birthday <- 1971-02-01
      meeting <- 09:30:00
      timestamp <- 2024-06-15T10:30:00Z

      // $ operator gives ISO 8601
      stdout.println(`Date ISO: ${birthday}`)
      stdout.println(`Time ISO: ${meeting}`)
      stdout.println(`DateTime ISO: ${timestamp}`)

      // Locale formatting with four levels
      enGB <- Locale("en_GB")
      enUS <- Locale("en_US")
      deutsch <- Locale("de_DE")

      // Date formatting
      stdout.println(`GB short: ${enGB.shortFormat(birthday)}`)
      stdout.println(`US short: ${enUS.shortFormat(birthday)}`)
      stdout.println(`GB medium: ${enGB.mediumFormat(birthday)}`)
      stdout.println(`DE long: ${deutsch.longFormat(birthday)}`)
      stdout.println(`GB full: ${enGB.fullFormat(birthday)}`)
      stdout.println(`DE full: ${deutsch.fullFormat(birthday)}`)

      // Time formatting
      stdout.println(`GB short time: ${enGB.shortFormat(meeting)}`)
      stdout.println(`DE medium time: ${deutsch.mediumFormat(meeting)}`)

      // DateTime formatting
      stdout.println(`US short DT: ${enUS.shortFormat(timestamp)}`)
      stdout.println(`GB medium DT: ${enGB.mediumFormat(timestamp)}`)
      stdout.println(`DE long DT: ${deutsch.longFormat(timestamp)}`)

Java: DateTimeFormatter.ofPattern('yyyy-MM-dd') pattern strings, multiple format letter conventions. Python: strftime('%Y-%m-%d') with % codes. JavaScript: toLocaleDateString() or Intl.DateTimeFormat. Go: bizarre reference date '2006-01-02'. Rust: chrono format!() with strftime codes. EK9: $ for ISO 8601, Locale.shortFormat/mediumFormat/longFormat/fullFormat for human-readable. No pattern strings.

date display duration format fullFormat iso8601 locale longFormat mediumFormat migrate pattern shortFormat string time timezone

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

Explanation: Locale has no generic format() method with a style parameter. EK9 provides four named methods: shortFormat(), mediumFormat(), longFormat(), fullFormat(). See ek9 -h E50060 for details.


Question: How do I parse a date from a string in EK9?

How do I convert a string to a date in EK9?

How do I handle invalid date strings in EK9?

How does Date construction from strings work?

Answer:
EK9 uses constructor parsing: pass a string to Date(), Time(), or DateTime() and use guard expressions to handle invalid input safely.

CONSTRUCTOR PARSING
Date('2024-01-15') parses ISO 8601 date format.
Time('10:30:00') parses ISO 8601 time format.
DateTime('2024-06-15T10:30:00Z') parses ISO 8601 datetime format.

INVALID INPUT PRODUCES UNSET
If the string is not a valid format, the constructor returns an unset value rather than throwing an exception:
  badDate <- Date('not-a-date')
  badDate? is false
This follows EK9's tri-state design: invalid parsing produces unset, not an error.

GUARD PATTERN FOR SAFE PARSING
Combine constructor parsing with guard expressions for clean error handling:
  if parsed <- Date(userInput)
    stdout.println(`Valid date: ${parsed}`)
  else
    stdout.println('Invalid date format')
The guard only enters the if-block when the parsed value is set.

ROUND-TRIPPING
The $ operator produces a string that can be parsed back:
  original <- 2024-06-15
  asString <- $original
  restored <- Date(asString)
  require restored == original

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

defines module qa.parse.date.string

  defines program
    ParseDateStringDemo()
      stdout <- Stdout()

      // Parse valid ISO 8601 strings
      parsedDate <- Date("2024-01-15")
      parsedTime <- Time("10:30:00")
      parsedDT <- DateTime("2024-06-15T10:30:00Z")

      stdout.println(`Parsed date: ${parsedDate}`)
      stdout.println(`Parsed time: ${parsedTime}`)
      stdout.println(`Parsed datetime: ${parsedDT}`)

      require parsedDate?
      require parsedTime?
      require parsedDT?

      // Invalid strings produce unset values
      badDate <- Date("not-a-date")
      badTime <- Time("invalid")
      badDT <- DateTime("garbage")

      require ~badDate?
      require ~badTime?
      require ~badDT?
      stdout.println(`Bad date isSet: ${badDate?}`)
      stdout.println(`Bad time isSet: ${badTime?}`)

      // Guard pattern for safe parsing
      userInput <- "2024-03-20"
      if validDate <- Date(userInput)
        stdout.println(`Valid: ${validDate}`)

      invalidInput <- "31/02/2024"
      if checkedDate <- Date(invalidInput)
        stdout.println(`This won't print`)
      else
        stdout.println("Invalid date format")

      // Round-tripping: $ produces parseable string
      original <- 2024-06-15
      asString <- $original
      restored <- Date(asString)
      require restored == original
      stdout.println(`Round-trip: ${original} -> ${asString} -> ${restored}`)

      // Duration parsing also returns unset on invalid
      validDur <- Duration("P1Y6M")
      invalidDur <- Duration("not-a-duration")
      require validDur?
      require ~invalidDur?
      stdout.println(`Valid duration: ${validDur}`)

Java: LocalDate.parse() throws DateTimeParseException on invalid input, requires try-catch. Python: datetime.strptime() throws ValueError, requires try-except. JavaScript: new Date('invalid') returns 'Invalid Date' object (truthy!). Go: time.Parse() returns error. Rust: NaiveDate::parse_from_str() returns Result. EK9: constructor returns unset value on invalid input, guard expression handles it cleanly.

constructor convert date duration guard invalid iso8601 isset null-safe parse round-trip safe string time timezone unset

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

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


Question: How do I calculate the difference between two dates in EK9?

How do I find the number of days between two dates?

Answer:
Subtract one temporal value from another using the - operator. The result is always a Duration.

DATE SUBTRACTION
  gap <- 2024-12-25 - 2024-01-01
The result is a Duration representing the span between the two dates. Use component accessors to extract parts:
  gap.days() gives the total days
  gap.hours() gives the total hours
  gap.years() gives the approximate years (30-day months)

TIME SUBTRACTION
  workHours <- 17:30 - 09:00
Gives the Duration between two times on the same day.

DATETIME SUBTRACTION
  elapsed <- laterEvent - earlierEvent
Handles timezone differences automatically: the result is the actual elapsed Duration regardless of timezone.

NEGATIVE DURATIONS
If you subtract a later date from an earlier one, the result is a negative Duration:
  negative <- 2024-01-01 - 2024-12-25
Negative durations are valid and useful.

COMPONENT ACCESSORS
Duration provides .years(), .months(), .days(), .hours(), .minutes(), .seconds() to extract individual components from the total.

See Q32 for Duration basics. See Q540 for adding and subtracting time. See Q542 for comparing dates. See Q550 for calendar edge cases.

defines module qa.date.difference

  defines program
    DateDifferenceDemo()
      stdout <- Stdout()

      // Date - Date gives Duration
      christmas <- 2024-12-25
      newYear <- 2024-01-01
      gap <- christmas - newYear
      stdout.println(`Days until Christmas: ${gap}`)
      stdout.println(`As days: ${gap.days()}`)

      // Time - Time gives Duration
      quittingTime <- 17:30
      startTime <- 09:00
      workDay <- quittingTime - startTime
      stdout.println(`Work day: ${workDay}`)
      stdout.println(`Work hours: ${workDay.hours()}`)

      // DateTime - DateTime gives Duration
      eventStart <- 2024-06-15T09:00:00Z
      eventEnd <- 2024-06-15T17:30:00Z
      eventLength <- eventEnd - eventStart
      stdout.println(`Event length: ${eventLength}`)

      // Negative duration when subtracting later from earlier
      backwards <- newYear - christmas
      stdout.println(`Backwards: ${backwards}`)

      // Component accessors on Duration
      longSpan <- 2026-06-15 - 2024-01-01
      stdout.println(`Years: ${longSpan.years()}`)
      stdout.println(`Months: ${longSpan.months()}`)
      stdout.println(`Days: ${longSpan.days()}`)
      stdout.println(`Hours: ${longSpan.hours()}`)
      stdout.println(`Minutes: ${longSpan.minutes()}`)
      stdout.println(`Seconds: ${longSpan.seconds()}`)

      // Cross-timezone DateTime subtraction
      nyMeeting <- 2024-06-15T09:00:00-04:00
      londonMeeting <- 2024-06-15T14:00:00+01:00
      tzGap <- londonMeeting - nyMeeting
      stdout.println(`Cross-timezone gap: ${tzGap}`)

Java: ChronoUnit.DAYS.between(d1, d2) or Period.between(d1, d2) separate APIs. Python: (date2 - date1).days attribute. JavaScript: manual millisecond subtraction and division. Go: t2.Sub(t1) returns time.Duration. Rust: chrono signed_duration_since(). EK9: simple date2 - date1 gives Duration, component accessors for parts.

between calculate calculation components date days difference duration elapsed gap negative subtract time timezone

Explanation: Date has no daysBetween() method. Use the - operator to subtract dates and get a Duration, then call .days() on the result for the day count. See ek9 -h E50060 for details.


Question: How do I add or subtract time from a date in EK9?

How does date arithmetic work with Duration?

Answer:
Use the + and - operators with Duration literals. EK9 supports both simple and compound assignment forms.

BASIC ARITHMETIC
  nextWeek <- 2024-01-15 + P7D
  lastMonth <- 2024-06-15 - P1M
  meetingEnd <- 2024-06-15T10:00:00Z + PT1H30M

COMPOUND ASSIGNMENT
  appointment <- 2024-03-01
  appointment += P2W
  appointment -= P3D

INCREMENT AND DECREMENT
  today <- Date().today()
  today++
  today--
For Date, ++ adds one day and -- subtracts one day.
For DateTime, ++ and -- also add/subtract one day.

THE DURATION SUMMING PITFALL
In languages with true calendar month arithmetic (Java, Python), month addition is NOT associative:
  Java: Jan 31 + 1 month = Feb 28 (clamped to month end)
        Feb 28 + 1 month = Mar 28
  But:  Jan 31 + 2 months = Mar 31 (different!)
Sequential single-month additions clamp at each step, losing information.

EK9 uses a predictable 30-day approximation for months:
  P1M = 30 days. P2M = 60 days. Always.
  date + P1M + P1M = date + 60 days
  date + P2M = date + 60 days (same result!)
Month arithmetic IS associative in EK9. The trade-off: P1M is always 30 days, not the actual number of days in any specific month.

For calendar-precise month logic, construct new dates from components (see Q550 for the deep dive).

MILLISECOND ARITHMETIC
You can also add Millisecond values:
  precise <- 2024-06-15T10:00:00Z + 5000ms

See Q32 for Duration literals. See Q539 for date subtraction. See Q550 for calendar edge cases and the full duration summing deep dive. See Q556 for Time wrapping.

defines module qa.add.subtract.time

  defines program
    AddSubtractTimeDemo()
      stdout <- Stdout()

      // Basic date + duration
      start <- 2024-01-15
      nextWeek <- start + P7D
      lastMonth <- start - P1M
      stdout.println(`Start: ${start}`)
      stdout.println(`+ P7D: ${nextWeek}`)
      stdout.println(`- P1M: ${lastMonth}`)

      // Complex duration
      future <- start + P1Y6M10D
      stdout.println(`+ P1Y6M10D: ${future}`)

      // Compound assignment
      appointment <- 2024-03-01
      appointment += P2W
      stdout.println(`+ P2W: ${appointment}`)
      appointment -= P3D
      stdout.println(`- P3D: ${appointment}`)

      // Increment and decrement (one day)
      mutableDate <- 2024-06-15
      mutableDate++
      stdout.println(`After ++: ${mutableDate}`)
      mutableDate--
      stdout.println(`After --: ${mutableDate}`)

      // DateTime arithmetic
      meeting <- 2024-06-15T10:00:00Z
      meetingEnd <- meeting + PT1H30M
      stdout.println(`Meeting end: ${meetingEnd}`)

      // Duration summing: EK9 is associative
      // P1M = 30 days always, so P1M + P1M = P2M = 60 days
      base <- 2024-01-15
      sequential <- base + P1M + P1M
      combined <- base + P2M
      stdout.println(`Sequential P1M + P1M: ${sequential}`)
      stdout.println(`Combined P2M: ${combined}`)
      require sequential == combined

      // Millisecond arithmetic
      precise <- 2024-06-15T10:00:00Z + 5000ms
      stdout.println(`+ 5000ms: ${precise}`)

      // Time arithmetic (wraps at 24h - see Q556)
      evening <- 22:00
      lateNight <- evening + PT3H
      stdout.println(`22:00 + PT3H: ${lateNight}`)

Java: date.plusDays(7), date.plusMonths(1) calendar-safe but non-associative (Jan 31 + 1 month + 1 month differs from Jan 31 + 2 months). Python: timedelta(days=7) for days only, relativedelta for months (same non-associativity). JavaScript: manual setDate/setMonth (notoriously broken). Go: t.AddDate(0, 1, 0) calendar-safe but non-associative. EK9: date + P7D, P1M = 30 days always, associative arithmetic. Trade-off: predictable vs calendar-precise.

add arithmetic associative calculate calendar date decrement duration increment migrate minus month pitfall plus subtract summing time timezone

Explanation: Date has no plusDays() method. EK9 uses the + operator with Duration literals (P7D for 7 days) for date arithmetic. See ek9 -h E50060 for details.


Question: How do I handle timezones in EK9?

How does EK9 handle timezone-aware dates?

How do I create a DateTime with a specific timezone?

Answer:
DateTime is EK9's timezone-aware type. It always carries a timezone. Use withSameInstant() to convert between timezones for display, and withZone() to reinterpret the clock reading in a different zone.

TIMEZONE IN LITERALS
UTC: 2024-06-15T10:30:00Z (the Z suffix means UTC)
Offset: 2024-06-15T10:30:00-05:00 (five hours behind UTC)
Offset: 2024-06-15T10:30:00+05:30 (India Standard Time)

TIMEZONE ACCESSORS
zone() returns the timezone string: 'Z', '-05:00', '+05:30'
offSetFromUTC() returns the offset as a Duration: PT0S for UTC, PT-5H for -05:00

CONVERTING TIMEZONES
withSameInstant(zoneId) converts the DISPLAY to another timezone while preserving the actual moment in time. The clock reading changes but the instant stays the same:
  utcMeeting <- 2024-06-15T10:30:00Z
  nyTime <- utcMeeting.withSameInstant('America/New_York')
  nyTime is 2024-06-15T06:30:00-04:00 (same instant, different clock)

withZone(zoneId) changes the timezone label WITHOUT converting the time. The clock reading stays but the instant changes:
  labeled <- utcMeeting.withZone('America/New_York')
  labeled is 2024-06-15T10:30:00-04:00 (same clock, different instant)

See Q546 for a detailed comparison of withSameInstant vs withZone.

IANA TIMEZONE NAMES
EK9 supports standard IANA timezone names:
  'America/New_York', 'Europe/London', 'Asia/Tokyo', 'Australia/Sydney'
Also supports UTC offsets: 'UTC', 'UTC+5', 'UTC-8'

See Q545 for timezone conversion patterns. See Q547 for UTC storage best practices. See Q548 for comparing across timezones. See Q553 for common timezone mistakes.

defines module qa.handle.timezones

  defines program
    HandleTimezonesDemo()
      stdout <- Stdout()

      // UTC literal
      utcMeeting <- 2024-06-15T10:30:00Z
      stdout.println(`UTC: ${utcMeeting}`)
      stdout.println(`Zone: ${utcMeeting.zone()}`)
      stdout.println(`Offset: ${utcMeeting.offSetFromUTC()}`)

      // Offset literals
      estEvent <- 2024-06-15T10:30:00-05:00
      indiaEvent <- 2024-06-15T10:30:00+05:30
      stdout.println(`EST: ${estEvent}, zone: ${estEvent.zone()}`)
      stdout.println(`IST: ${indiaEvent}, zone: ${indiaEvent.zone()}`)

      // withSameInstant: same moment, different clock
      nyTime <- utcMeeting.withSameInstant("America/New_York")
      tokyoTime <- utcMeeting.withSameInstant("Asia/Tokyo")
      londonTime <- utcMeeting.withSameInstant("Europe/London")
      stdout.println(`NY: ${nyTime}`)
      stdout.println(`Tokyo: ${tokyoTime}`)
      stdout.println(`London: ${londonTime}`)

      // withZone: same clock, different timezone label
      relabeled <- utcMeeting.withZone("America/New_York")
      stdout.println(`Relabeled: ${relabeled}`)

      // The key difference:
      // withSameInstant changes the clock to show the same moment
      // withZone keeps the clock and changes the timezone
      // So nyTime and relabeled show different times!
      stdout.println(`Same instant NY hour: ${nyTime.hour()}`)
      stdout.println(`Relabeled NY hour: ${relabeled.hour()}`)

      // Offset accessor returns Duration
      estOffset <- estEvent.offSetFromUTC()
      stdout.println(`EST offset: ${estOffset}`)

Java: ZonedDateTime with ZoneId, withZoneSameInstant() and withZoneSameLocal() (confusing names). Python: datetime with pytz/zoneinfo, astimezone() for conversion. JavaScript: no built-in timezone support, Intl.DateTimeFormat or moment-timezone. Go: time.In(location) for conversion. Rust: chrono with_timezone(). EK9: DateTime built-in with zone(), withSameInstant() and withZone(), IANA names supported.

convert datetime duration iana offset time timezone utc withSameInstant withZone zone

Explanation: DateTime has no toTimezone() method. Use withSameInstant() to convert a DateTime to another timezone while preserving the same instant. See ek9 -h E50060 for details.


Question: How do I compare dates in EK9?

How do I check if one date is before another?

What comparison operators work on dates?

Answer:
All temporal types (Date, Time, DateTime, Duration, Millisecond) support the full set of comparison operators.

COMPARISON OPERATORS
  == equal
  <> not equal
  < less than (earlier)
  > greater than (later)
  <= less than or equal
  >= greater than or equal
  <=> spaceship (returns -1, 0, or 1)
  <~> fuzzy comparison

DATE COMPARISON
Dates compare chronologically:
  2024-01-01 < 2024-12-25 is true
  2024-06-15 == 2024-06-15 is true

TIME COMPARISON
Times compare within the day:
  09:00 < 17:00 is true

DATETIME COMPARISON — UTC NORMALIZED
DateTimes in different timezones compare by their UTC-equivalent instant:
  2024-06-15T10:00:00Z == 2024-06-15T06:00:00-04:00 is true
  (Both represent the same moment)
This means you can safely compare timestamps from different timezones.

SPACESHIP OPERATOR
The <=> operator returns an Integer: negative (before), zero (equal), positive (after). Useful for custom sorting logic.

FUZZY COMPARISON
The <~> operator returns an Integer representing approximate distance. For dates this is the absolute difference in days.

SORTING
Since all comparison operators are defined, temporal values work naturally in sorted collections and stream sort operations.

See Q31 for Date and Time basics. See Q548 for cross-timezone comparison details.

defines module qa.compare.dates

  defines program
    CompareDatesDemo()
      stdout <- Stdout()

      // Date comparison
      newYear <- 2024-01-01
      christmas <- 2024-12-25
      summer <- 2024-06-15
      sameSummer <- 2024-06-15

      require newYear < christmas
      require christmas > newYear
      require summer == sameSummer
      require newYear <> christmas
      require newYear <= summer
      require christmas >= summer
      stdout.println(`Jan < Dec: ${newYear < christmas}`)
      stdout.println(`Jun == Jun: ${summer == sameSummer}`)

      // Time comparison
      morning <- 09:00
      evening <- 17:00
      require morning < evening
      stdout.println(`09:00 < 17:00: ${morning < evening}`)

      // DateTime comparison — UTC normalized
      utcNoon <- 2024-06-15T12:00:00Z
      nyMorning <- 2024-06-15T08:00:00-04:00
      // Both represent the same instant (12:00 UTC = 08:00 EDT)
      require utcNoon == nyMorning
      stdout.println(`UTC noon == NY 8am: ${utcNoon == nyMorning}`)

      // Different instants in different zones
      laterNY <- 2024-06-15T14:00:00-04:00
      require laterNY > utcNoon
      stdout.println(`NY 2pm > UTC noon: ${laterNY > utcNoon}`)

      // Spaceship operator
      ordering <- newYear <=> christmas
      stdout.println(`Jan <=> Dec: ${ordering}`)
      require ordering < 0

      // Fuzzy comparison
      fuzzy <- newYear <~> christmas
      stdout.println(`Fuzzy distance: ${fuzzy}`)

      // Duration comparison
      shortDur <- PT30M
      longDur <- PT2H
      require shortDur < longDur
      stdout.println(`30min < 2h: ${shortDur < longDur}`)

      // Millisecond comparison
      shortMs <- 100ms
      longMs <- 500ms
      require shortMs < longMs
      require longMs > shortMs
      stdout.println(`100ms < 500ms: ${shortMs < longMs}`)

Java: compareTo(), isBefore(), isAfter(), isEqual() methods. Python: direct comparison operators work on datetime objects. JavaScript: compare via getTime() milliseconds (operators on Date objects are unreliable). Go: t.Before(), t.After(), t.Equal() methods. Rust: PartialOrd trait. EK9: full operator set (==, <>, <, >, <=, >=, <=>, <~>) on all temporal types, UTC-normalized DateTime comparison.

after before chronological compare date datetime duration equal fuzzy operators sort spaceship time timezone

Explanation: Date has no isBefore() method. EK9 uses comparison operators (<, >, <=, >=, ==, <>) directly on temporal types. See ek9 -h E50060 for details.


Question: How do I work with Unix timestamps in EK9?

How do I convert epoch milliseconds to a date?

How do I get the Unix epoch time in EK9?

How does Millisecond relate to epoch time?

Answer:
EK9 uses the Millisecond type for epoch-based timestamps. SystemClock().millisecond() returns the current Unix epoch time in milliseconds.

GETTING EPOCH TIME
  epochNow <- SystemClock().millisecond()
This returns a Millisecond value representing milliseconds since 1970-01-01T00:00:00Z (the Unix epoch).

DATE FROM EPOCH DAYS
The Date constructor accepts days from epoch:
  Date(daysFromEpoch) creates a date from the number of days since 1970-01-01.
The #< operator extracts epoch days from a Date:
  epochDays <- #< myDate

DATETIME AND EPOCH
DateTime does not have a direct epoch constructor, but you can work with epoch values through Duration and Millisecond arithmetic:
  epochMs <- SystemClock().millisecond()
  asDuration <- epochMs.duration()

MILLISECOND AS TIMESTAMP
Millisecond is the natural type for Unix timestamps, API timestamps, and database epoch values. It carries type information (this is milliseconds, not nanoseconds or seconds) and supports arithmetic.

See Q41 for Millisecond basics. See Q544 for benchmarking with Millisecond. See Q536 for getting current date and time.

defines module qa.unix.timestamps

  defines program
    UnixTimestampsDemo()
      stdout <- Stdout()

      // Current epoch time in milliseconds
      epochNow <- SystemClock().millisecond()
      stdout.println(`Epoch now: ${epochNow}`)

      // Date from epoch days
      // 1970-01-01 is day 0
      unixEpoch <- Date(0)
      stdout.println(`Epoch date: ${unixEpoch}`)

      // Extract epoch days from a date
      today <- Date().today()
      epochDays <- #< today
      stdout.println(`Today's epoch days: ${epochDays}`)

      // Round-trip: epoch days back to date
      restored <- Date(epochDays)
      require restored == today
      stdout.println(`Restored: ${restored}`)

      // Millisecond to Duration conversion
      fiveSeconds <- 5000ms
      asDuration <- fiveSeconds.duration()
      stdout.println(`5000ms as Duration: ${asDuration}`)

      // Millisecond arithmetic for timestamp differences
      earlier <- SystemClock().millisecond()
      stdout.println("Some work here")
      later <- SystemClock().millisecond()
      elapsed <- later - earlier
      stdout.println(`Elapsed: ${elapsed}`)

      // Known epoch dates
      y2k <- Date(10957)
      stdout.println(`Y2K (day 10957): ${y2k}`)

Java: System.currentTimeMillis() returns raw long, Instant.toEpochMilli(), LocalDate.toEpochDay(). Python: time.time() returns float seconds, datetime.timestamp(). JavaScript: Date.now() returns milliseconds, new Date(epochMs). Go: time.Now().UnixMilli(). Rust: SystemTime::now().duration_since(UNIX_EPOCH). EK9: SystemClock().millisecond() returns typed Millisecond, Date(epochDays) for date construction, #< for epoch extraction.

convert days duration epoch millisecond seconds systemclock time timestamp timezone unix

Explanation: SystemClock has no currentTimeMillis() method. Use millisecond() to get the current epoch time as a typed Millisecond value. See ek9 -h E50060 for details.


Question: How do I measure elapsed time for benchmarking in EK9?

How do I time how long something takes in EK9?

How do I use SystemClock for performance measurement?

How do I profile code execution time in EK9?

Answer:
Use SystemClock().millisecond() before and after the code section, then subtract to get elapsed Millisecond.

BASIC TIMING PATTERN
  startMs <- SystemClock().millisecond()
  // ... code to measure ...
  endMs <- SystemClock().millisecond()
  elapsed <- endMs - startMs
  stdout.println(`Elapsed: ${elapsed}`)

The result is a typed Millisecond value, not a raw integer. You can convert it to Duration with .duration() or get the raw value.

BUILT-IN PROFILING FLAGS
EK9 also has compiler-level profiling:
  ek9 -P program.ek9 runs with profiling enabled
  ek9 -Pf program.ek9 profiles with full detail
The compiler instruments the code automatically. For micro-benchmarks, manual SystemClock timing gives you control over exactly what is measured.

MILLISECOND ARITHMETIC
You can accumulate timing results:
  totalMs <- 0ms
  totalMs += elapsed
Or compute averages:
  averageMs <- totalMs / iterations

CONVERT TO DURATION
  elapsed.duration() converts to Duration for display in hours/minutes/seconds format.

See Q41 for Millisecond type details. See Q543 for Unix timestamps. See Q322 for the profiling system. See Q631 for benchmarking two approaches.

defines module qa.elapsed.time.benchmarking

  defines program
    ElapsedTimeBenchmarkingDemo()
      stdout <- Stdout()

      // Basic timing pattern
      startMs <- SystemClock().millisecond()

      // Simulate work with a loop
      counter <- 0
      for i in 1 ... 1000
        counter += i

      endMs <- SystemClock().millisecond()
      elapsed <- endMs - startMs
      stdout.println(`Loop elapsed: ${elapsed}`)
      stdout.println(`Counter: ${counter}`)

      // Convert to Duration
      asDuration <- elapsed.duration()
      stdout.println(`As Duration: ${asDuration}`)

      // Accumulate multiple measurements
      totalMs <- 0ms
      for run in 1 ... 3
        runStart <- SystemClock().millisecond()
        sum <- 0
        for j in 1 ... 500
          sum += j
        runEnd <- SystemClock().millisecond()
        runElapsed <- runEnd - runStart
        totalMs += runElapsed
        stdout.println(`Run ${run}: ${runElapsed}`)

      stdout.println(`Total: ${totalMs}`)

      // Millisecond comparison for thresholds
      threshold <- 1000ms
      if totalMs < threshold
        stdout.println("Fast enough")
      else
        stdout.println("Too slow")

Java: System.nanoTime() for benchmarking (not currentTimeMillis which has clock adjustment issues). Python: time.perf_counter() or timeit module. JavaScript: performance.now() for high-resolution timing. Go: time.Now() with time.Since(). Rust: std::time::Instant::now() with elapsed(). EK9: SystemClock().millisecond() with typed subtraction, built-in -P profiling flag.

benchmark date duration elapsed measure millisecond performance profile systemclock time timezone timing

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

Explanation: EK9 uses SystemClock().millisecond(), not static Java-style methods. SystemClock is instantiated then millisecond() is called on it. Triggers E50001 — method not resolved. See ek9 -h SystemClock for the full API.


Question: How do I convert a DateTime to a different timezone in EK9?

How do I display a time in a different timezone?

How do I convert UTC to local time in EK9?

How does withSameInstant work for timezone conversion?

Answer:
Use withSameInstant(zoneId) to convert a DateTime to show the same moment in a different timezone. This is the most common timezone operation.

CONVERTING FOR DISPLAY
  utcEvent <- 2024-06-15T14:00:00Z
  nyDisplay <- utcEvent.withSameInstant('America/New_York')
  tokyoDisplay <- utcEvent.withSameInstant('Asia/Tokyo')
  londonDisplay <- utcEvent.withSameInstant('Europe/London')
All three represent the SAME moment. Only the clock reading and timezone label change.

COMMON IANA ZONE NAMES
  Americas: America/New_York, America/Chicago, America/Denver, America/Los_Angeles, America/Sao_Paulo
  Europe: Europe/London, Europe/Paris, Europe/Berlin, Europe/Moscow
  Asia: Asia/Tokyo, Asia/Shanghai, Asia/Kolkata, Asia/Dubai
  Pacific: Australia/Sydney, Pacific/Auckland
  UTC: UTC, Etc/UTC

PATTERN: STORE UTC, DISPLAY LOCAL
Store all timestamps in UTC. Convert to the user's timezone only for display:
  stored <- 2024-06-15T14:00:00Z
  userZone <- 'America/New_York'
  displayed <- stored.withSameInstant(userZone)
This avoids timezone confusion in comparisons and arithmetic.

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

defines module qa.convert.timezone

  defines program
    ConvertTimezoneDemo()
      stdout <- Stdout()

      // UTC event
      utcEvent <- 2024-06-15T14:00:00Z
      stdout.println(`UTC: ${utcEvent}`)

      // Convert to various timezones
      nyTime <- utcEvent.withSameInstant("America/New_York")
      chicagoTime <- utcEvent.withSameInstant("America/Chicago")
      laTime <- utcEvent.withSameInstant("America/Los_Angeles")
      londonTime <- utcEvent.withSameInstant("Europe/London")
      parisTime <- utcEvent.withSameInstant("Europe/Paris")
      tokyoTime <- utcEvent.withSameInstant("Asia/Tokyo")
      sydneyTime <- utcEvent.withSameInstant("Australia/Sydney")

      stdout.println(`New York: ${nyTime}`)
      stdout.println(`Chicago: ${chicagoTime}`)
      stdout.println(`Los Angeles: ${laTime}`)
      stdout.println(`London: ${londonTime}`)
      stdout.println(`Paris: ${parisTime}`)
      stdout.println(`Tokyo: ${tokyoTime}`)
      stdout.println(`Sydney: ${sydneyTime}`)

      // All are the same instant
      require nyTime == utcEvent
      require tokyoTime == utcEvent
      require sydneyTime == utcEvent
      stdout.println(`All equal UTC: ${nyTime == utcEvent and tokyoTime == utcEvent}`)

      // Store UTC, display local pattern
      stored <- 2024-12-25T00:00:00Z
      userZone <- "Europe/Berlin"
      displayed <- stored.withSameInstant(userZone)
      stdout.println(`Stored: ${stored}`)
      stdout.println(`User sees: ${displayed}`)

Java: dateTime.withZoneSameInstant(ZoneId.of('America/New_York')). Python: dt.astimezone(zoneinfo.ZoneInfo('America/New_York')). JavaScript: no built-in, Intl.DateTimeFormat for display only. Go: t.In(loc). Rust: chrono with_timezone(). EK9: dateTime.withSameInstant('America/New_York') clear and concise.

convert display duration iana local time timezone utc withSameInstant zone

Explanation: DateTime has no convertTo() method. Use withSameInstant() to convert a DateTime to show the same moment in a different timezone. See ek9 -h E50060 for details.


Question: What is the difference between withSameInstant() and withZone() in EK9?

When should I use withSameInstant vs withZone?

How do withSameInstant and withZone differ?

Answer:
These two methods serve fundamentally different purposes. Confusing them is one of the most common timezone bugs.

withSameInstant(zoneId): SAME MOMENT, DIFFERENT CLOCK
Converts the display to show the same physical instant in a different timezone. The moment in time stays the same, but the clock reading changes:
  10:00 UTC .withSameInstant('America/New_York') = 06:00 EDT
  Same moment: both represent the same point on the timeline.
  Use case: 'What time is it in New York RIGHT NOW?'

withZone(zoneId): SAME CLOCK, DIFFERENT MOMENT
Keeps the clock reading (hour, minute, second) but changes the timezone label. This creates a DIFFERENT moment in time:
  10:00 UTC .withZone('America/New_York') = 10:00 EDT
  Different moment: 10:00 UTC and 10:00 EDT are 4 hours apart.
  Use case: 'The meeting is at 10:00 in the New York office' (when you have the local time as UTC by mistake).

THE CRITICAL DIFFERENCE
withSameInstant: preserves the INSTANT, changes the DISPLAY
withZone: preserves the DISPLAY, changes the INSTANT

Most timezone work uses withSameInstant. Use withZone only when you know the clock reading is correct but the timezone label is wrong (e.g., importing data where times were recorded in local time but stored as UTC).

See Q541 for timezone basics. See Q545 for timezone conversion patterns. See Q547 for UTC storage practices. See Q553 for common timezone mistakes.

defines module qa.withsameinstant.vs.withzone

  defines constant
    newYorkZone <- "America/New_York"

  defines program
    WithSameInstantVsWithZoneDemo()
      stdout <- Stdout()

      utcTime <- 2024-06-15T10:00:00Z
      stdout.println(`Original UTC: ${utcTime}`)

      // withSameInstant: same moment, different clock
      nyInstant <- utcTime.withSameInstant(newYorkZone)
      stdout.println(`withSameInstant NY: ${nyInstant}`)
      stdout.println(`  Hour: ${nyInstant.hour()}`)
      // The hour changes (10:00 UTC -> 06:00 EDT)
      // But they represent the SAME moment
      require utcTime == nyInstant

      // withZone: same clock, different moment
      nyZone <- utcTime.withZone(newYorkZone)
      stdout.println(`withZone NY: ${nyZone}`)
      stdout.println(`  Hour: ${nyZone.hour()}`)
      // The hour stays 10 but the timezone changes
      // They represent DIFFERENT moments (4 hours apart)
      require utcTime <> nyZone

      // Visual comparison
      stdout.println("---")
      stdout.println(`UTC:             ${utcTime}`)
      stdout.println(`Same instant NY: ${nyInstant}`)
      stdout.println(`Same clock NY:   ${nyZone}`)

      // The difference between the two results
      instantDiff <- nyZone - nyInstant
      stdout.println(`Difference: ${instantDiff}`)

      // Real-world example: Tokyo office meeting at 15:00 local
      tokyoMeeting <- 2024-06-15T15:00:00+09:00
      // What time should NY team join? (withSameInstant)
      nyJoinTime <- tokyoMeeting.withSameInstant(newYorkZone)
      stdout.println(`Tokyo 15:00 = NY ${nyJoinTime.hour()}:00`)

Java: withZoneSameInstant() vs withZoneSameLocal() (confusing names, easy to mix up). Python: astimezone() for same-instant, replace(tzinfo=) for same-clock (dangerous). JavaScript: no built-in distinction. Go: In() for same-instant only. Rust: chrono with_timezone() for same-instant. EK9: withSameInstant() and withZone() with clear descriptive names.

clock convert display duration instant moment time timezone withSameInstant withZone

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

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