Skip to content

Replace dayjs with Temporal #167

Description

@niallroche

Replace dayjs-backed now in template execution with Temporal-compatible current time support

Summary

Accord Project templates currently receive a runtime now value during drafting and execution. In the current stack this appears to be backed by dayjs, via @accordproject/markdown-template and @accordproject/template-engine.

Now that TC39 Temporal is Stage 4, it may be worth introducing Temporal-compatible current time support in template-engine, either as a new template variable or as a long-term replacement path for the existing now.

Current Behavior

APAP and other consumers of the library call the template engine like this:

const templateArchiveProcessor = new TemplateArchiveProcessor(apTemplate);
const draftResult = await templateArchiveProcessor.draft(agreement.data, format, {});

TemplateArchiveProcessor already exposes deterministic current-time parameters:
draft(data, format, options, currentTime?)
trigger(data, request, state?, currentTime?, utcOffset?)
init(data, currentTime?, utcOffset?)

From the current dependency stack, now is injected downstream in @accordproject/markdown-template roughly as:
```ts
data.now = now ? now : dayjs();

with temporalNow derived from the existing currentTime parameter, preserving deterministic execution while giving template authors access to modern date/time semantics.

Motivation

  • Temporal gives a more precise and standard model for contract/template time handling:
  • exact instants via Temporal.Instant
  • explicit time zones via Temporal.ZonedDateTime
  • date-only and time-only values via Temporal.PlainDate, Temporal.PlainTime, etc.
  • safer DST-aware arithmetic
  • strict string parsing and serialisation
  • no reliance on legacy Date behaviour

This is particularly relevant for legal agreement templates where “now”, effective dates, delivery windows, time zones, and duration arithmetic need deterministic and auditable semantics.

Compatibility Concern

This should probably not be a direct silent replacement of now.
Existing templates may rely on dayjs-style behaviour, for example:

{{% return now.toLocaleString() %}}

or other dayjs methods. Replacing now with a Temporal object would likely be breaking.

Possible Approach

Introduce Temporal support additively first:

temporalNow // Temporal.Instant or Temporal.ZonedDateTime
now         // existing dayjs-compatible value

Potential options:

  1. Add temporalNow alongside now.
  2. Allow engine options to choose the runtime time representation.
  3. Keep now dayjs-compatible for the 2.x line.
  4. Consider migrating now itself only in a future major version.
  5. Accept an injected current-time provider so runtimes can supply deterministic Temporal.Instant / Temporal.ZonedDateTime values.

Open Questions

  1. Should temporalNow be a Temporal.Instant, Temporal.ZonedDateTime, or both?
  2. Should templates default to UTC or receive an explicit IANA time zone?
  3. Should currentTime remain an ISO string at the public API boundary?
  4. Should utcOffset be replaced or complemented by an IANA time zone identifier?
  5. Should Temporal support require Node 26+, or should @js-temporal/polyfill be used while Node 22/24 are still supported?

Suggested Direction

A non-breaking first step would be to expose a new Temporal-compatible value in template expressions while keeping the existing now behaviour unchanged.

For example:

{{% return temporalNow.toString() %}}

with temporalNow derived from the existing currentTime parameter, preserving deterministic execution while giving template authors access to modern date/time semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions