-
Notifications
You must be signed in to change notification settings - Fork 3.7k
fix(cron): apply timezone from schedule.TZ for cron expressions #1046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
150bb34
a8f2666
c3281a5
038a55e
d784651
6a5ee16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ import ( | |
| "github.com/sipeed/picoclaw/pkg/fileutil" | ||
| ) | ||
|
|
||
| const defaultTimezone = "UTC" | ||
|
|
||
| type CronSchedule struct { | ||
| Kind string `json:"kind"` | ||
| AtMS *int64 `json:"atMs,omitempty"` | ||
|
|
@@ -66,6 +68,7 @@ type CronService struct { | |
| running bool | ||
| stopChan chan struct{} | ||
| gronx *gronx.Gronx | ||
| defaultTZ string // default timezone for cron expressions | ||
| } | ||
|
|
||
| func NewCronService(storePath string, onJob JobHandler) *CronService { | ||
|
|
@@ -266,6 +269,19 @@ func (cs *CronService) computeNextRun(schedule *CronSchedule, nowMS int64) *int6 | |
|
|
||
| // Use gronx to calculate next run time | ||
| now := time.UnixMilli(nowMS) | ||
| // Apply timezone: schedule.TZ > service default > UTC | ||
| tz := schedule.TZ | ||
| if tz == "" { | ||
| tz = cs.defaultTZ | ||
| } | ||
| if tz == "" { | ||
| tz = defaultTimezone | ||
| } | ||
|
Comment on lines
+272
to
+279
|
||
| if loc, err := time.LoadLocation(tz); err == nil { | ||
| now = now.In(loc) | ||
| } else { | ||
| log.Printf("[cron] warning: failed to load timezone '%s': %v, using UTC", tz, err) | ||
| } | ||
| nextTime, err := gronx.NextTickAfter(schedule.Expr, now, false) | ||
| if err != nil { | ||
| log.Printf("[cron] failed to compute next run for expr '%s': %v", schedule.Expr, err) | ||
|
|
@@ -313,6 +329,14 @@ func (cs *CronService) SetOnJob(handler JobHandler) { | |
| cs.onJob = handler | ||
| } | ||
|
|
||
| // SetDefaultTimezone sets the default timezone for cron expressions. | ||
| // If empty, falls back to UTC. | ||
| func (cs *CronService) SetDefaultTimezone(tz string) { | ||
| cs.mu.Lock() | ||
| defer cs.mu.Unlock() | ||
| cs.defaultTZ = tz | ||
| } | ||
|
|
||
| func (cs *CronService) loadStore() error { | ||
| cs.store = &CronStore{ | ||
| Version: 1, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,147 @@ | ||||||||
| package cron | ||||||||
|
|
||||||||
| import ( | ||||||||
| "path/filepath" | ||||||||
| "testing" | ||||||||
| "time" | ||||||||
|
||||||||
| "time" | |
| "time" | |
| _ "time/tzdata" |
Copilot
AI
Mar 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test uses the legacy alias timezone ID US/Eastern. That alias isnβt guaranteed to exist across all zoneinfo distributions, which can make the test fail on some environments even though the code is correct. Prefer a canonical IANA zone like America/New_York (and then you can assert the exact expected UTC hour for the chosen date).
Copilot
AI
Mar 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this test the reference date is 2026-03-04, when US/Eastern is not in DST, so the next run should be deterministically 14:00 UTC. Allowing 13 or 14 makes the assertion weaker and could let an off-by-one-hour bug slip through; consider asserting the exact expected hour (or computing it from the loaded location for the chosen date).
Copilot
AI
Mar 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This subtest special-cases the US/Eastern case by comparing on tt.name, which is easy to break if the display name changes. Consider branching on tt.tz (or adding a boolean in the test cases) so the behavior under test is keyed off the timezone value itself.
Copilot
AI
Mar 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cs.SetDefaultTimezone("US/Eastern") relies on a legacy timezone alias that may not be present in all tzdata bundles. Using a canonical IANA name like America/New_York makes the test (and the intended config value) more portable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default timezone literal "Asia/Shanghai" is duplicated in both the scheduling logic and method comment. Defining a single package-level constant (and reusing it in tests) would reduce the chance of docs/tests drifting from behavior if this fallback is ever changed.