Skip to content

feat(frontend): show local timezone alongside UTC in schedule displays#1564

Open
machadovilaca wants to merge 5 commits into
ambient-code:mainfrom
machadovilaca:feat/schedule-timezone-display
Open

feat(frontend): show local timezone alongside UTC in schedule displays#1564
machadovilaca wants to merge 5 commits into
ambient-code:mainfrom
machadovilaca:feat/schedule-timezone-display

Conversation

@machadovilaca
Copy link
Copy Markdown
Contributor

@machadovilaca machadovilaca commented May 12, 2026

Summary

Follows up on #1496. Shows the user's local timezone alongside UTC in all schedule-related displays, so users don't have to mentally convert UTC times.

  • Add getCronDescriptionWithLocal() that inserts "UTC" after the time in cron descriptions and appends the local equivalent in parentheses
  • Update schedule column, details card, and form preview to use the new function
  • Add "Times are in UTC" helper text to the schedule form
  • Sub-daily schedules (every hour, every 15 min) skip the annotation since local offset adds no value

Before: At 05:30 AM, Monday through Friday
After: At 05:30 AM UTC, Monday through Friday (6:30 AM GMT+1)

Edit
s1

List
s2

Show
s3

Why UTC input (not local-first input)

We considered letting users input cron schedules in their local timezone and converting to UTC behind the scenes, but rejected it:

  • DST drift: getTimezoneOffset() returns the offset at input time, not execution time. A user in London (UTC+0 winter, UTC+1 summer) who sets "9 AM local" in January gets 0 9 * * * UTC — which silently becomes 10 AM local in summer
  • Fractional offsets: UTC+5:30 (India, 1.4B people), UTC+5:45 (Nepal), UTC+12:45 (Chatham Islands) require shifting both hour and minute fields, with minute overflow creating double-wrap edge cases
  • Day-of-week wrapping: Timezone shifts crossing midnight require adjusting day-of-week fields — ranges like 1-5 must become 0-4 or 2-6, with modular wrapping on each element
  • Round-trip conversion: Editing an existing schedule requires reverse-converting UTC→local, which may produce a different cron than originally entered if DST changed since creation

UTC input avoids all of these. Showing the local equivalent next to it gives users the "when does this fire for me?" answer without any conversion complexity or correctness risks.

Test plan

  • Unit tests for getCronDescriptionWithLocal() — daily, weekly, monthly schedules include UTC + local; sub-daily schedules return plain description; invalid cron falls back gracefully
  • All frontend unit tests pass
  • Production build passes with 0 errors, 0 warnings
  • Visual verification against Kind cluster: schedule list, detail card, and form preview all show dual timezone

Summary by CodeRabbit

  • New Features

    • Schedule descriptions now include a UTC label and, when appropriate, a parenthetical local-time preview for clarity across time zones.
    • Schedule preview areas explicitly note "Times are in UTC".
    • Table and detail labels simplified to "Schedule" while showing the enhanced timezone-aware descriptions.
  • Tests

    • Added tests validating the timezone-aware schedule description behavior.
  • Documentation

    • Added a frontend spec detailing timezone display rules for cron-based schedules.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 12, 2026

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 3d81632
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a038905f8bede000809121e

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: af0ee992-6bf2-47c1-a749-0282d2e2583f

📥 Commits

Reviewing files that changed from the base of the PR and between ec0d103 and ad58202.

📒 Files selected for processing (6)
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx
  • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx
  • components/frontend/src/lib/__tests__/cron.test.ts
  • components/frontend/src/lib/cron.ts
  • specs/frontend/schedule-timezone-display.spec.md
✅ Files skipped from review due to trivial changes (2)
  • specs/frontend/schedule-timezone-display.spec.md
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx
  • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx
  • components/frontend/src/lib/cron.ts
  • components/frontend/src/lib/tests/cron.test.ts

📝 Walkthrough

Walkthrough

A new helper, getCronDescriptionWithLocal, augments human-readable cron descriptions with optional UTC/local time context based on next-run calculations. Three frontend components were updated to use it and removed explicit “(UTC)” labels; the form also shows a “Times are in UTC” note. Tests and a spec were added.

Changes

Cron Description Localization

Layer / File(s) Summary
Helper and exported API
components/frontend/src/lib/cron.ts
Added export function getCronDescriptionWithLocal(cronExpr: string): string that builds a base cronstrue description, computes the next two UTC runs, returns the plain description when runs are insufficient or interval < 24h (sub-daily), otherwise appends UTC to the first time token and formats the first run as local time with formatTimeLocal, returning either the UTC-only label or "<UTC> (<local>)" depending on whether the local string already ends with UTC.
Details card integration
components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx
Replaced getCronDescription import with getCronDescriptionWithLocal; changed rendered label from “Schedule (UTC)” to “Schedule”; uses getCronDescriptionWithLocal(scheduledSession.schedule) for the human-readable description while preserving the raw scheduledSession.schedule string display.
Form preview integration
components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx
Swapped import to getCronDescriptionWithLocal; computes cron preview via getCronDescriptionWithLocal(effectiveCron); added an inline “Times are in UTC” explanatory note in the schedule/cron preview area.
Tab/table integration
components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx
Replaced getCronDescription with getCronDescriptionWithLocal; memoizes a cronDescriptions map from items keyed by session name and renders cronDescriptions.get(ss.name) in the schedule cell; changed column header from “Schedule (UTC)” to “Schedule”.
Tests and spec
components/frontend/src/lib/__tests__/cron.test.ts, specs/frontend/schedule-timezone-display.spec.md
Adds describe('getCronDescriptionWithLocal', ...) tests covering UTC labeling for daily/weekday/weekly/monthly, conditional parenthesized local-time for daily schedules when browser timezone ≠ UTC, omission for sub-daily schedules (matching getCronDescription), invalid-cron fallback, and a regex check for UTC placement; adds a frontend spec documenting the expected timezone display rules and detection logic.
sequenceDiagram
    participant Browser
    participant Component as Details/Form/Tab
    participant CronLib as cron.ts:getCronDescriptionWithLocal
    participant Runner as getNextRuns
    participant Formatter as formatTimeLocal

    Browser->>Component: render scheduled session UI
    Component->>CronLib: getCronDescriptionWithLocal(cronExpr)
    CronLib->>Runner: getNextRuns(cronExpr, 2)
    Runner-->>CronLib: nextRun1, nextRun2
    alt interval < 24h (sub-daily)
        CronLib-->>Component: base cronstrue description
    else daily-or-longer
        CronLib->>Formatter: formatTimeLocal(nextRun1)
        Formatter-->>CronLib: localTimeStr
        CronLib-->>Component: "UTC-labeled description" or "UTC-labeled description (localTimeStr)"
    end
    Component-->>Browser: render description + raw cron string
Loading

Possibly related PRs

  • ambient-code/platform#1496: Modifies next-run parsing to use {tz: "UTC"}; affects the next-run logic getCronDescriptionWithLocal relies on.
  • ambient-code/platform#1106: Edits scheduled-session form/details UI touching the same components updated here.
  • ambient-code/platform#864: Related frontend cron utility changes and scheduled-sessions UI updates; overlaps in where cron descriptions are produced and consumed.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Performance And Algorithmic Complexity ❌ Error Unoptimized getCronDescriptionWithLocal() in scheduled-session-details-card.tsx line 58: called directly every render without memoization, unlike form and tab components. Wrap call in useMemo with [scheduledSession.schedule] dependency, matching scheduled-session-form.tsx and scheduled-sessions-tab.tsx patterns.
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits format with type 'feat' and scope 'frontend', and accurately summarizes the main change of displaying local timezone alongside UTC in schedule displays.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security And Secret Handling ✅ Passed Frontend-only change adding timezone display. No hardcoded secrets, dangerous constructors, injection vulnerabilities in regex, K8s Secret issues, or missing auth. Clean security posture.
Kubernetes Resource Safety ✅ Passed PR contains only frontend TypeScript/React components and specifications; no Kubernetes resource manifests or infrastructure configurations. Check not applicable.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/frontend/src/lib/__tests__/cron.test.ts`:
- Around line 42-45: The test for getCronDescriptionWithLocal is wrong: it
claims "when not UTC" but only checks for "UTC", so it doesn't validate the
local parenthesized suffix; update the test to mock/stub the local timezone
formatter to return a non-UTC timezone (e.g. override
Intl.DateTimeFormat.prototype.resolvedOptions or the formatter used by
getCronDescriptionWithLocal to return timeZone: 'America/Los_Angeles'), call
getCronDescriptionWithLocal('0 9 * * *'), and assert explicitly that the
returned string contains the parenthesized local annotation (for example assert
it matches a regex like /\([^)]+\)$/ and contains the mocked timezone
identifier).

In `@components/frontend/src/lib/cron.ts`:
- Around line 42-49: The current sub-daily detection uses a calendar-date
equality check (sameDay) on runs[0] and runs[1], which misclassifies schedules
near UTC day boundaries; change the logic to compute the time interval between
runs (e.g., runs[1].getTime() - runs[0].getTime()) and treat it as sub-daily if
that difference is strictly less than 24 * 60 * 60 * 1000 ms, replacing the
sameDay check so the code paths that return description for sub-daily schedules
use the interval comparison instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5296c7ea-9610-42f9-b188-1d0b1be869e9

📥 Commits

Reviewing files that changed from the base of the PR and between 28874a9 and f17be9f.

📒 Files selected for processing (5)
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx
  • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx
  • components/frontend/src/lib/__tests__/cron.test.ts
  • components/frontend/src/lib/cron.ts

Comment thread components/frontend/src/lib/__tests__/cron.test.ts Outdated
Comment thread components/frontend/src/lib/cron.ts Outdated
@machadovilaca machadovilaca force-pushed the feat/schedule-timezone-display branch 2 times, most recently from 16977f0 to ac518f2 Compare May 12, 2026 14:07
@machadovilaca
Copy link
Copy Markdown
Contributor Author

/cc @jsell-rh

Copy link
Copy Markdown
Contributor

@jsell-rh jsell-rh left a comment

Choose a reason for hiding this comment

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

cron.ts:52 — the regex (\d{1,2}:\d{2}\s*[AP]M) assumes 12-hour format. cronstrue can return 24-hour time (e.g. 14:30) in non-English locales, so the regex won't match and the "UTC" label silently disappears. Not a crash, just no annotation.

Consider matching 24h as well: (\d{1,2}:\d{2}(\s*[AP]M)?).

@jsell-rh
Copy link
Copy Markdown
Contributor

@machadovilaca thank you!

Review bot found one nit, and I've got one more request:

We're moving towards a spec-driven workflow for ambient. Could you use the /spec skill to create/update the specs to reflect this change?

We don't have full spec coverage at this point, but I do want to make sure this change is captured in the specs.

Feel free to include those spec changes in this PR.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/frontend/src/lib/cron.ts`:
- Around line 52-60: The regex replacement that creates withUtc can fail
silently and lead to inconsistent dual-timezone strings; update the logic in
this block to first run const match =
description.match(/(\d{1,2}:\d{2}\s*[AP]M)/) and only build withUtc and append
the local time when match is truthy—i.e., if match exists set withUtc =
description.replace(... ) (or explicitly `${match[1]} UTC`) and then apply the
existing local.endsWith("UTC") check; if no match, return description (or
`${description} (${formatTimeLocal(runs[0])})` only if you intend to always
include local) to ensure you don't append a local time when the UTC label wasn't
inserted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8479b18c-fe95-4aeb-9c91-e81d308dd4d3

📥 Commits

Reviewing files that changed from the base of the PR and between f17be9f and ec0d103.

📒 Files selected for processing (5)
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx
  • components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx
  • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx
  • components/frontend/src/lib/__tests__/cron.test.ts
  • components/frontend/src/lib/cron.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • components/frontend/src/app/projects/[name]/scheduled-sessions/[scheduledSessionName]/_components/scheduled-session-details-card.tsx
  • components/frontend/src/lib/tests/cron.test.ts
  • components/frontend/src/app/projects/[name]/scheduled-sessions/_components/scheduled-session-form.tsx
  • components/frontend/src/components/workspace-sections/scheduled-sessions-tab.tsx

Comment thread components/frontend/src/lib/cron.ts Outdated
machadovilaca and others added 2 commits May 12, 2026 18:16
Formalizes the frontend behavior for displaying cron schedule times:
UTC label on daily-or-longer schedules, local timezone parenthetical
for non-UTC browsers, sub-daily omission, interval-based detection,
graceful fallback, and 12h/24h format support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add getCronDescriptionWithLocal() that enriches cron descriptions with
the user's local timezone. "At 05:30 AM, Monday through Friday" becomes
"At 05:30 AM UTC, Monday through Friday (6:30 AM GMT+1)".

Sub-daily schedules (every hour, every 15 min) skip the annotation since
the local offset adds no value.

Applied consistently across:
- Scheduled sessions list (Schedule column)
- Scheduled session detail card
- Create/edit form preview

Follows up on ambient-code#1496 which fixed cron parsing to use UTC. We chose UTC
input with local display (rather than local-first input) to avoid DST
drift, fractional offset conversion bugs, and day-of-week wrapping edge
cases documented in the design spec.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@machadovilaca machadovilaca force-pushed the feat/schedule-timezone-display branch from ec0d103 to 6d44002 Compare May 12, 2026 17:16
@jsell-rh
Copy link
Copy Markdown
Contributor

LGTM

@jsell-rh
Copy link
Copy Markdown
Contributor

@machadovilaca looks like lint might be failing

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.

2 participants