Skip to content

feat(formula): timezone-aware today() / daysFromNow() / daysAgo() - #1998

Merged
os-zhuang merged 1 commit into
mainfrom
feat/tz-time-functions-1980
Jun 17, 2026
Merged

feat(formula): timezone-aware today() / daysFromNow() / daysAgo()#1998
os-zhuang merged 1 commit into
mainfrom
feat/tz-time-functions-1980

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Part of #1980. The compute-tz core of ADR-0053 Phase 2, building on the resolver (#1978).

What

today() / daysFromNow() / daysAgo() become calendar-day functions resolved in a reference timezone. The zone is threaded ExecutionContext.timezone (#1978) → EvalContext.timezone → CEL stdlib, and the timezone math uses Intl.DateTimeFormat (DST-safe, no hand-rolled offsets).

Each returns the reference-tz calendar day expressed as a UTC-midnight Date — decision D1 in the ADR. This is the one representation consistent with:

So record.close_date == daysFromNow(30) now matches in-memory too — not just at the storage boundary. (Avoids the "local-midnight-as-instant" trap that would re-introduce the off-by-one.)

⚠️ Behavior change

daysFromNow(n) / daysAgo(n) previously kept now's wall-clock time (daysFromNow(30) at 10:00Z…T10:00:00Z). They now drop the time and return the calendar day at midnight (…T00:00:00Z) — the ADR-0053 "defect #3" fix, and what makes date-equality work in-memory.

  • today() is unchanged at UTC (already start-of-day).
  • For a genuine sub-day offset, use the documented escape hatch now() + duration("Nh") (cel-js duration() is supported — confirmed by existing tests).
  • With no reference timezone configured, the zone resolves to UTC: today() is byte-for-byte unchanged; only the daysFromNow/daysAgo midnight-truncation differs from before.

The two pre-existing tests that asserted the old wall-clock behavior are updated to the calendar-day semantic. A scan found no other package relies on the old time-carrying behavior.

Wiring

objectql threads execCtx.timezone into read-time formula evaluation (applyFormulaPlan, from #1979) and default-value expressions (applyFieldDefaults).

Testing

  • New: tz-aware today()/daysFromNow()/daysAgo() across the America/New_York day boundary; daysFromNow midnight semantics; UTC default unchanged for today().
  • @objectstack/formula121 green, @objectstack/objectql639 green; DTS build + typecheck clean.
  • Changeset: minor.

Note: consuming a non-UTC reference timezone end-to-end also needs the discoverable localization settings manifest noted in #1978 (a small follow-up). The resolver + this slice are wired; an org just can't set the value via UI yet.

🤖 Generated with Claude Code

ADR-0053 Phase 2 D1. today()/daysFromNow()/daysAgo() become calendar-day
functions resolved in a reference timezone (threaded ExecutionContext.timezone
-> EvalContext.timezone -> CEL stdlib). Each returns the reference-tz calendar
day as a UTC-midnight Date — the representation consistent with Field.date
hydration, the SQL driver's date-filter normalization, and Phase 1 storage —
so `record.date == daysFromNow(n)` matches in-memory too. Timezone math uses
Intl.DateTimeFormat (DST-safe).
BEHAVIOR CHANGE: daysFromNow(n)/daysAgo(n) previously kept now's wall-clock
time; they now drop it and return the calendar day at midnight (the ADR
"defect #3" fix). today() is unchanged at UTC. Sub-day offsets: now() +
duration("Nh").
objectql threads execCtx.timezone into applyFormulaPlan (read-time formula
fields) and applyFieldDefaults (default-value expressions). With no reference
tz configured the zone is UTC.
Tests: tz-aware today()/daysFromNow()/daysAgo() (America/New_York day
boundary); daysFromNow midnight semantics; existing wall-clock assertions
updated to calendar-day. formula 121 green, objectql 639 green.
Part of #1980.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercelBot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJun 17, 2026 2:46am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Jun 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/formula, @objectstack/objectql.

16 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/core/services.mdx(via @objectstack/objectql)
  • content/docs/concepts/implementation-status.mdx(via @objectstack/objectql)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/objectql)
  • content/docs/concepts/packages.mdx(via @objectstack/formula, @objectstack/objectql)
  • content/docs/guides/authentication.mdx(via @objectstack/objectql)
  • content/docs/guides/deployment-vercel.mdx(via @objectstack/objectql)
  • content/docs/guides/formula.mdx(via @objectstack/formula, packages/objectql)
  • content/docs/guides/kernel-services.mdx(via @objectstack/objectql)
  • content/docs/guides/metadata/validation.mdx(via @objectstack/formula)
  • content/docs/guides/objectql-migration.mdx(via @objectstack/objectql)
  • content/docs/guides/packages.mdx(via @objectstack/formula, @objectstack/objectql)
  • content/docs/guides/plugins.mdx(via @objectstack/objectql)
  • content/docs/protocol/objectos/index.mdx(via @objectstack/objectql)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/objectql)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/formula)
  • content/docs/releases/v9.mdx(via @objectstack/objectql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit d99a75a into mainJun 17, 2026
14 checks passed
@os-zhuang
os-zhuang deleted the feat/tz-time-functions-1980 branch June 17, 2026 03:27
os-zhuang added a commit that referenced this pull request Jul 18, 2026
Add acceptance tests for the timezone-aware today()/daysFromNow()/daysAgo()
functions (compute-tz core of ADR-0053 Phase 2, decision D1). The
implementation already shipped (#1998/#2001/#2006); these lock the issue's
criteria and pin the DST-boundary + equality behavior:
- AC1: today() at 2026-06-16T02:00Z in America/Los_Angeles == UTC-midnight of
2026-06-15.
- AC3: reference tz unset vs 'UTC' is byte-for-byte the pre-Phase-2 behavior
for all three functions.
- AC2: calendar days are correct across both 2026 US DST transitions
(spring-forward Mar 8, fall-back Nov 1); a Field.datetime instant compares
equal to daysFromNow(n) across DST; a Field.date string matches via the
hydration-safe idioms (ordering operators, date(), daysBetween()).
- A characterization guard documents the known cel-js equality limitation:
a bare `date-string == today()` silently returns false because cel-js's
isEqual hard-codes `string == X` to false. This is timezone-independent and
cross-cutting; the fix belongs in the data layer (hydrate date fields to Date
where field types are known) and is tracked as a separate follow-up.
Test-only; no changeset (no functional change).
Claude-Session: https://claude.ai/code/session_01SuiM565BZ3TR1VD3prMguB
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang