Part of ADR-0053 Phase 2. Design: #1975 · Parent: #1928.
Slice 3 of 6 — compute-tz core. Make the time functions timezone-aware. Gated behind "reference tz unset → UTC".
Decision D1 (see #1975)
today()/daysFromNow()/daysAgo() return a Date at UTC-midnight of the reference-tz calendar day — new Date(Date.UTC(y, m, d)) with (y,m,d) computed in the reference tz. Not a date-only string; not local-midnight-as-instant (that trap re-introduces the silent-miss bug — cel-js's hydrateOverloadStrings rehydrates the field's date-only string to UTC midnight, so today() must match that).
Scope
- Add
timezone?: string to EvalContext — packages/formula/src/types.ts.
registerStdLib(env, now) → registerStdLib(env, now, timezone); buildEnv(now) → buildEnv(now, ctx.timezone) — packages/formula/src/stdlib.ts:40-57, packages/formula/src/cel-engine.ts:35-42 and :189-191.
- Replace
startOfDayUtc/addDaysUtc (stdlib.ts:18-29) with tz-aware equivalents built on a shared partsInTz/calendarDayUtc util extracted from the proven Intl.formatToParts pattern in packages/services/service-messaging/src/preference-resolver.ts:347. No hand-rolled offset math (DST).
- Side-effect: also fixes the "keeps wall-clock time" defect of
daysFromNow/daysAgo.
Acceptance criteria
Prerequisite
Confirm cel-js supports duration() + Timestamp arithmetic (the documented now() + duration("Nh") escape hatch). No duration usage exists in the formula package today.
Depends on
Slice 1 (timezone value); produces the shared partsInTz util reused by slice 5.
Part of ADR-0053 Phase 2. Design: #1975 · Parent: #1928.
Slice 3 of 6 — compute-tz core. Make the time functions timezone-aware. Gated behind "reference tz unset → UTC".
Decision D1 (see #1975)
today()/daysFromNow()/daysAgo()return aDateat UTC-midnight of the reference-tz calendar day —new Date(Date.UTC(y, m, d))with(y,m,d)computed in the reference tz. Not a date-only string; not local-midnight-as-instant (that trap re-introduces the silent-miss bug — cel-js'shydrateOverloadStringsrehydrates the field's date-only string to UTC midnight, sotoday()must match that).Scope
timezone?: stringtoEvalContext—packages/formula/src/types.ts.registerStdLib(env, now)→registerStdLib(env, now, timezone);buildEnv(now)→buildEnv(now, ctx.timezone)—packages/formula/src/stdlib.ts:40-57,packages/formula/src/cel-engine.ts:35-42and:189-191.startOfDayUtc/addDaysUtc(stdlib.ts:18-29) with tz-aware equivalents built on a sharedpartsInTz/calendarDayUtcutil extracted from the provenIntl.formatToPartspattern inpackages/services/service-messaging/src/preference-resolver.ts:347. No hand-rolled offset math (DST).daysFromNow/daysAgo.Acceptance criteria
America/Los_Angeles, at2026-06-16T02:00Z,today()== the UTC-midnight Date of2026-06-15.record.due_date == today()and== daysFromNow(n)match the right calendar day across DST boundaries.'UTC'→ byte-for-byte today's behavior.Prerequisite
Confirm cel-js supports
duration()+ Timestamp arithmetic (the documentednow() + duration("Nh")escape hatch). Nodurationusage exists in the formula package today.Depends on
Slice 1 (timezone value); produces the shared
partsInTzutil reused by slice 5.