feat(formula,email): render datetime in reference timezone (ADR-0053 Phase 2) (#1981)#2003
Merged
Conversation
…Phase 2, #1981) datetime template holes render in a reference timezone's wall-clock at the presentation boundary; storage stays UTC. Calendar-day `date` rendering is intentionally unaffected (tz-naive). - formula: the `datetime` formatter takes tz from EvalContext.timezone (#1980) → Intl.DateTimeFormat; `datetime:iso` stays UTC. New exported `formatValue(name, value, arg, { locale, timeZone })` so the whitelisted formatters are reusable outside the CEL template engine. - plugin-email: the renderer bypassed the formatter pipeline (raw String()), so a datetime went out as raw ISO. Email holes now accept the shared formula formatters via formatValue (single source of truth), keeping HTML-escaping and `{{{ }}}` raw-output. SendTemplateInput.timezone (mirroring locale) flows into rendering so a datetime shows the recipient's wall-clock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 89 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The formatter-aware regex used two overlapping `\s*` groups (inside the
optional `| formatter` group and trailing), which CodeQL flagged as
js/polynomial-redos (slow on `{{{{.` + many tabs). Switch to a brace-free
inner capture (`[^{}]*` — single star, no nested quantifier) and parse the
hole body with plain string ops, the same linear strategy the formula
template engine uses. Adds a regression test on the flagged input shape.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of ADR-0053 Phase 2 (Slice 4 of 6 — render-tz). Closes #1981. Design: #1975 · Parent: #1928.
Shows
datetimeinstants in a reference timezone at the presentation boundary. Storage stays UTC. This is the last of the six Phase 2 slices.Changes
Centralized path — formula template engine (low blast radius)
datetimeformatter takes the reference timezone fromEvalContext.timezone(threaded in ADR-0053 Phase 2 · Slice 3: timezone-aware today()/daysFromNow()/daysAgo() #1980) and passes it toIntl.DateTimeFormat.{{ ts | datetime }}now renders in that zone;{{ ts | datetime:iso }}stays UTC (machine-readable, unambiguous).daterendering is intentionally unchanged — aField.dateis tz-naive, so applying a zone would shift the day (acceptance criterion 3).formatValue(name, value, arg, { locale, timeZone })— the single source of truth for value→string semantics, reusable by renderers that don't run the full CEL template engine.Outlier — email pipeline (the architectural cost)
plugin-email's renderer bypassed the formatter pipeline entirely (String()only), so a datetime was emailed as raw ISO. Now:{{ order.total | currency }},{{ ts | datetime }}— by reusingformatValue(so email formatting is identical to in-app). The engine keeps its HTML-escaping +{{{ }}}raw-output semantics and its lenient "never throw on render" contract (unknown formatter → raw value).SendTemplateInput.timezone(mirroring the existinglocale) flows into rendering, so the caller supplies the recipient's / tenant's zone (typically fromExecutionContext.timezone).plugin-emailgains a@objectstack/formuladependency (acyclic; formula is a leaf).Acceptance criteria
{{ value | datetime }}renders in the reference tz when set; UTC/runtime when unset.date(calendar-day) rendering is unaffected (tz-naive after Phase 1).Tests
template-formatters.test.ts— datetime in NY vs UTC;datetime:isostays UTC;date:isounaffected;formatValuereuse + unknown-formatterundefined.plugin-email/template-engine.test.ts— formatter holes, tz datetime, escaping preserved ({{ }}vs{{{ }}}), unknown-formatter fallback, missing value → ''.send-template.test.ts— end-to-end: an email datetime renders ininput.timezone, not raw ISO.Depends on
Slice 1 (timezone value #1978) and #1980 — both merged.
🤖 Generated with Claude Code