Skip to content

fix(tables): canonicalize date cells, render times in effective timezone#5465

Merged
TheodoreSpeaks merged 3 commits into
stagingfrom
fix/table-date-rendering
Jul 7, 2026
Merged

fix(tables): canonicalize date cells, render times in effective timezone#5465
TheodoreSpeaks merged 3 commits into
stagingfrom
fix/table-date-rendering

Conversation

@TheodoreSpeaks

@TheodoreSpeaks TheodoreSpeaks commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • date cells store exactly two canonical shapes: YYYY-MM-DD calendar dates (timezone-free) and RFC 3339 instants with preserved offset (2026-07-06T16:04:55-07:00) — server coercion and CSV import normalize on write, legacy rows self-heal
  • display is wall-clock-faithful: every viewer sees the literal wall time that was written (07/06/2026 4:04 PM, seconds when present) — no per-viewer conversion; the offset suffix keeps filters/sorts/agents on the true instant
  • the writer's effective timezone (settings → browser) is applied once, at write time, to stamp naive input; untouched edits re-save byte-identical so a viewer in another zone can't silently re-stamp a value
  • emcn Calendar single mode gains showTime (mirrors range mode) — inline cell editor gets a time field, day picks preserve the existing time instead of silently dropping it; row modal uses ChipDatePicker + ChipTimePicker
  • imports interpret naive datetimes (Excel/Sheets exports) in the user's timezone: request field → saved setting → UTC; threaded through both sync and async import paths + job payload
  • fixed Calendar highlighting the UTC day for ISO instant values, and displayToStorage accepting rolled-over dates like 02/30

Type of Change

  • Bug fix

Testing

Unit tests for the canonical date module (offset preservation, zone stamping, DST, exporter wire formats) and the display/parse round-trips — wall-clock display assertions are now deterministic across runner timezones; full lint, type-check, check:api-validation:strict pass. Inline editor focus/blur behavior needs a manual browser pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 7, 2026 7:27pm

Request Review

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes how date values are stored, validated, and coerced across imports and inline edits—incorrect timezone stamping could shift instants for existing workflows, though behavior is heavily unit-tested.

Overview
Table date cells now normalize on write to either a calendar YYYY-MM-DD or an RFC 3339 instant with a preserved offset, instead of ad-hoc ISO conversion. Shared logic in lib/table/dates drives server validation, CSV coercion, and client parse/display so everyone sees the literal wall time that was stored (optional seconds in editors), with naive datetimes stamped once using the writer’s effective IANA zone.

CSV import (sync, async, and background jobs) accepts an optional timezone, validates it, defaults from user settings → UTC, and threads it through coerceRowsForTable so Excel/Sheets-style naive datetimes import consistently. Client upload hooks send the user’s timezone.

The grid and row modal gain date + time editing (Calendar showTime, ChipTimePicker, improved blur/focus and “unchanged draft” saves so offsets aren’t re-stamped). Display/paste/expanded editor paths use the new helpers; invalid calendar days (e.g. 02/30) are rejected.

Reviewed by Cursor Bugbot for commit 11a9151. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread packages/emcn/src/components/calendar/calendar.tsx
@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the previous "store-as-ISO, display in viewer's timezone" model for table date cells with a wall-clock-faithful canonical form: calendar dates stay as YYYY-MM-DD (timezone-free) and datetimes are stored as RFC 3339 with the writer's preserved offset (2026-07-06T16:04:55-07:00). Display is deterministic — every viewer sees exactly the literal wall time that was written.

  • Adds lib/table/dates.ts (pure, shared between server coercion and client rendering) with normalizeDateCellValue, formatDateCellDisplay, and related utilities; the Calendar single mode gains showTime and an optional today prop for effective-timezone anchoring.
  • Threads the user's effective timezone through all CSV import paths (sync, async, drag-drop) and through the inline editor, expanded-cell editor, and row modal so naive datetimes are stamped once at write time with the correct offset.
  • Fixes Calendar UTC-day highlighting for offset instants, displayToStorage accepting rolled-over dates like 02/30, and the midnight guard that silently dropped time for zone-local midnight instants.

Confidence Score: 5/5

The change is safe to merge. The core date canonicalization logic is well-tested and correct; all three previously identified bugs (midnight guard, rolled-over calendar days, UTC-day highlighting) are fixed.

The new dates.ts module is pure and thoroughly unit-tested across DST months, idempotency, legacy UTC-midnight detection, and wall-clock display invariants. The write path (inline editor, expanded editor, row modal, paste, CSV import) consistently stamps naive datetimes with the writer's effective timezone and preserves explicit offsets byte-identical. The untouched-draft fast-path in the inline editor prevents re-stamping stored offsets with a different viewer's zone. No data-loss or incorrect-offset path was found.

No files require special attention. The only minor items are doc-comment consistency in dates.ts (the barrel now re-exports it, making the old "never use the barrel" warning slightly misleading) and the documented DST fall-back ambiguity in wallTimeInZoneToUtc.

Important Files Changed

Filename Overview
apps/sim/lib/table/dates.ts New pure module for canonical date-cell semantics. Two-pass DST resolution, wall-clock display, and offset extraction are well-designed. extractExplicitOffsetMinutes only runs after Date.parse succeeds and TIME_COMPONENT_PATTERN matches, limiting false-positive risk.
apps/sim/lib/table/dates.test.ts Comprehensive tests covering idempotency, DST months, explicit-offset preservation, UTC-midnight legacy detection, and wall-clock display. Deterministic across runner timezones via localOffsetSuffix helper.
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/utils.ts Adds dateValueToLocalParts, localPartsToDateValue, todayLocalCalendarDate; rewires displayToStorage with isValidCalendarDay round-trip check and withTime regex. All previously flagged bugs (rolled-over dates, timezone re-stamping) resolved.
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/inline-editors.tsx Untouched-draft fast-path correctly re-saves stored bytes without re-stamping the offset. draftRef prevents stale-closure in scheduled blur. popoverPointerAtRef avoids premature blur-save during picker interaction.
packages/emcn/src/components/calendar/calendar.tsx Adds showTime and today to single mode. Pivots from useEffect to render-phase state update for view navigation (valid React pattern). parseDateTimeValue correctly keeps time for T strings even at midnight.
apps/sim/lib/table/import.ts Switches coerceValue for dates from new Date().toISOString() to normalizeDateCellValue, threading NormalizeDateCellOptions through coerceRowsForTable. Correct, minimal change.
apps/sim/lib/table/validation.ts Server-side coercion now normalizes date strings via normalizeDateCellValue (without timezone — naive API inputs get the server's UTC offset, which is documented). Previously accepted any Date.parse-valid string as-is; now canonicalizes.
apps/sim/hooks/queries/tables.ts All four import mutation hooks now obtain timezone from useTimezone() and forward it in the request body or form data. Consistent across sync/async paths.
apps/sim/lib/table/index.ts Re-exports dates.ts through the server-tainted barrel. dates.ts module comment warns client code against using the barrel, but the barrel now exports the same symbols — the warning becomes misleading for future readers.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as UI (browser)
    participant Utils as utils.ts
    participant Dates as dates.ts
    participant Server as API route
    participant DB as Database

    Note over UI,DB: Write path (inline editor / row modal / paste)
    UI->>Utils: displayToStorage(draft, timeZone)
    Utils->>Dates: "normalizeDateCellValue(raw, {timezone})"
    Note right of Dates: explicit offset → preserve as-is<br/>naive datetime → stamp with zone offset<br/>date-only → YYYY-MM-DD
    Dates-->>Utils: canonical (2026-07-06T16:04:55-07:00)
    Utils-->>UI: canonical value
    UI->>Server: "PATCH /table/{id}/row"
    Server->>Dates: normalizeDateCellValue(value)
    Dates-->>Server: normalized
    Server->>DB: INSERT canonical value

    Note over UI,DB: Read / display path
    DB-->>UI: stored value
    UI->>Dates: "formatDateCellDisplay(stored, {seconds})"
    Note right of Dates: YYYY-MM-DD → MM/DD/YYYY<br/>T00:00:00Z → UTC day (legacy)<br/>offset instant → literal wall time
    Dates-->>UI: 07/06/2026 4:04 PM

    Note over UI,DB: CSV import path
    UI->>Server: "POST /import {file, timezone}"
    Server->>Dates: "normalizeDateCellValue(cellValue, {timezone})"
    Note right of Dates: naive Excel/Sheets value stamped with user zone
    Dates-->>Server: canonical
    Server->>DB: INSERT canonical value
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as UI (browser)
    participant Utils as utils.ts
    participant Dates as dates.ts
    participant Server as API route
    participant DB as Database

    Note over UI,DB: Write path (inline editor / row modal / paste)
    UI->>Utils: displayToStorage(draft, timeZone)
    Utils->>Dates: "normalizeDateCellValue(raw, {timezone})"
    Note right of Dates: explicit offset → preserve as-is<br/>naive datetime → stamp with zone offset<br/>date-only → YYYY-MM-DD
    Dates-->>Utils: canonical (2026-07-06T16:04:55-07:00)
    Utils-->>UI: canonical value
    UI->>Server: "PATCH /table/{id}/row"
    Server->>Dates: normalizeDateCellValue(value)
    Dates-->>Server: normalized
    Server->>DB: INSERT canonical value

    Note over UI,DB: Read / display path
    DB-->>UI: stored value
    UI->>Dates: "formatDateCellDisplay(stored, {seconds})"
    Note right of Dates: YYYY-MM-DD → MM/DD/YYYY<br/>T00:00:00Z → UTC day (legacy)<br/>offset instant → literal wall time
    Dates-->>UI: 07/06/2026 4:04 PM

    Note over UI,DB: CSV import path
    UI->>Server: "POST /import {file, timezone}"
    Server->>Dates: "normalizeDateCellValue(cellValue, {timezone})"
    Note right of Dates: naive Excel/Sheets value stamped with user zone
    Dates-->>Server: canonical
    Server->>DB: INSERT canonical value
Loading

Reviews (2): Last reviewed commit: "fix(tables): address review — preserve m..." | Re-trigger Greptile

Comment thread packages/emcn/src/components/calendar/calendar.tsx
Comment thread apps/sim/app/workspace/[workspaceId]/tables/[tableId]/utils.ts
Comment thread packages/emcn/src/components/calendar/calendar.tsx

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 992f362. Configure here.

Comment thread packages/emcn/src/components/calendar/calendar.tsx
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks TheodoreSpeaks merged commit 619e912 into staging Jul 7, 2026
18 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the fix/table-date-rendering branch July 7, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant