Skip to content

refactor(web): compute schedule previews with a timezone-safe time model - #44065

Open
JHC56 wants to merge 9 commits into
langgenius:mainfrom
JHC56:refactor/schedule-time-model
Open

JHC56 wants to merge 9 commits into
langgenius:mainfrom
JHC56:refactor/schedule-time-model

Conversation

@JHC56

@JHC56 JHC56 commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Part of #43937 (step 2)

Problem

The schedule preview does not always show the real runs. Near a DST change, a job can run at a different time or run twice, but the preview shows something else. Some cron expressions show runs in the preview, but the backend never runs them.

Steps to reproduce (Docker)

  1. In the docker folder, point the web service at the current main image and start Dify.
   printf 'services:\n  web:\n    image: langgenius/dify-web:main\n' > docker-compose.override.yaml
   docker compose pull web
   docker compose up -d
  1. Open a workflow and add a Schedule Trigger node. Set the timezone to America/New_York, the frequency to Daily and the time to 02:30 AM.
  2. Open the browser console and paste this helper. The helper freezes the browser clock at Mar 7, 2026, 11 PM in New York.
   const RealDate = Date
   window.setNow = (iso) => {
     const fixed = new RealDate(iso).getTime()
     window.Date = class extends RealDate {
       constructor(...args) { super(...(args.length ? args : [fixed])) }
       static now() { return fixed }
     }
   }
   setNow('2026-03-08T04:00:00Z')
  1. Change the time to another value and back to 02:30 AM, so "Next 5 execution times" is recalculated.
  2. The first run shows March 8, 2026, 2:30 AM (UTC-4). This time doesn't exist in New York, and the backend runs the job at 3:00 AM.
Screenshot 2026-10-14 173431

Expected

Preview shows the same runs as the backend, in any browser timezone.

Actual

The preview can show a time that does not exist, miss some runs, or move a time by one hour.

This happens because the preview keeps schedule times in browser-local Date objects, so the times follow the browser's DST rules. The preview also uses cron-parser, which reads some cron syntax differently from croniter on the backend.


Changes

  • time-model.ts (new) keeps wall-clock time and real instants apart, so the browser timezone no longer changes the result.
  • cron-parser.ts finds next runs the same way as croniter on the backend. [Refactor/Chore] Schedule Trigger time model: separate wall-clock times from instants #43937 planned to keep cron-parser for parsing, but the library reads some syntax differently, so parsing follows croniter too. cron-parser is now only used for validation.
  • execution-time-calculator.ts turns visual settings into cron like visual_to_cron, so visual and cron share one path.
  • timezone.ts no longer exports getTimezoneOffsetMinutes, since no other file uses the function.
  • The rest follows the [Refactor/Chore] Schedule Trigger time model: separate wall-clock times from instants #43937 plan as written. WallTime and instants are kept apart. toInstants finds DST gaps and folds, and toWall reads the clock in the schedule timezone. No browser-local Date math is left.

Frontend only, execution behavior is unchanged. DST handling follows what the backend does today. The new DST rules come in step 3.

Tests

  • New next-runs.spec.ts for DST gap and fold (New York, London, Dublin, Santiago, Lord Howe, Chatham), weekly order, cron syntax, and a check that no browser-local Date method is called.
  • integration.spec.ts now checks wall-clock fields in the schedule timezone instead of browser-local getHours().

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've verified the change and added or updated tests where meaningful regression risk justifies coverage.
  • I've updated the documentation accordingly.
  • I ran make lint && make type-check (backend) and vp staged (frontend) to appease the lint gods

@JHC56
JHC56 requested review from iamjoel and zxhlyh as code owners October 14, 2026 10:23
@ghfind-review ghfind-review Bot added the review: medium ghfind author score; see https://ghfind.com label Oct 14, 2026
@github-actions github-actions Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. web This relates to changes on the web. labels Oct 14, 2026
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.15282% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.86%. Comparing base (c4e0116) to head (eca95a4).

Files with missing lines Patch % Lines
...rkflow/nodes/trigger-schedule/utils/cron-parser.ts 88.61% 32 Missing ⚠️
...rigger-schedule/utils/execution-time-calculator.ts 97.91% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #44065    +/-   ##
========================================
  Coverage   89.85%   89.86%            
========================================
  Files        6250     6251     +1     
  Lines      398212   398414   +202     
  Branches    80242    80311    +69     
========================================
+ Hits       357827   358042   +215     
+ Misses      34074    34061    -13     
  Partials     6311     6311            
Flag Coverage Δ
dify-ui 92.39% <ø> (ø)
web 89.86% <91.15%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JHC56

JHC56 commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

@crazywoola I updated this PR to the latest main. The weekly test from #44718 now reads times in the schedule timezone, because the preview now returns real run times. I also added tests for monthly previews and for the case with no next run.

The new parser follows croniter, so it also handles syntax like 15W. The current cron check still blocks that syntax, so this part of the parser is not used yet. I will align the check in a follow-up PR.

Could you take a look when you have time?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: medium ghfind author score; see https://ghfind.com size:XXL This PR changes 1000+ lines, ignoring generated files. web This relates to changes on the web.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant