Skip to content

fix: honor cron trigger timezone when scheduling runs - #7798

Open
anxkhn wants to merge 2 commits into
flyteorg:mainfrom
anxkhn:fix/cron-trigger-timezone
Open

fix: honor cron trigger timezone when scheduling runs#7798
anxkhn wants to merge 2 commits into
flyteorg:mainfrom
anxkhn:fix/cron-trigger-timezone

Conversation

@anxkhn

@anxkhn anxkhn commented Aug 6, 2026

Copy link
Copy Markdown

Why are the changes needed?

The structured Cron schedule carries a timezone, and trigger validation already prepends CRON_TZ= before parsing it. The scheduler does not: ParseSchedule passes only Cron.expression to cron.ParseStandard, and the cron runner defaults to UTC. Every accepted non-UTC trigger therefore executes in UTC.

Concretely, a trigger deployed as 0 9 * * * with timezone America/Los_Angeles passes validation but fires at 09:00 UTC instead of 09:00 Pacific. Users get runs several hours early or late, the offset shifts again across daylight-saving transitions, and catch-up time calculations inherit the same error. CRON_TZ is the per-schedule timezone override documented by robfig/cron, so the information is available, it is just dropped on the runtime path.

What changes were proposed in this pull request?

  • Add a shared ParseCron helper that handles both schedule forms: the structured Cron message (applying CRON_TZ=<timezone> when a timezone is set) and the legacy cron_expression string.
  • Use it in ParseSchedule so live scheduling and catch-up calculations respect the declared timezone.
  • Use it in validateCronExpression so deploy-time validation and runtime parsing can no longer disagree about what a schedule means.

One behavior change worth calling out: because validation now goes through the same parser, an invalid legacy cron_expression is rejected at registration. Previously only the structured Cron form was validated, and a bad legacy expression was accepted and then failed later inside the scheduler.

How was this patch tested?

New table tests in the scheduler core cover UTC, a non-UTC zone, and a DST boundary (7-8 March 2026 in America/Los_Angeles), asserting both the next fire time and the catch-up times. New validation tests cover structured cron with a timezone, legacy cron, and invalid expressions in both forms.

go test ./runs/service ./runs/scheduler/core

Check all the applicable boxes

  • All new and existing tests passed.
  • All commits are signed-off.

Related PRs

None.

Docs link

None.

anxkhn added 2 commits August 6, 2026 14:22
Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 6, 2026 14:42
@github-actions github-actions Bot added the flyte2 label Aug 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a scheduling bug where cron triggers with a declared timezone were validated as timezone-aware but executed as UTC at runtime, causing triggers to fire at the wrong wall-clock time (including across DST transitions). It centralizes cron parsing so both deploy-time validation and runtime scheduling/catch-up calculations interpret schedules consistently.

Changes:

  • Introduces a shared schedule.ParseCron helper that parses both structured cron schedules (honoring Cron.timezone via CRON_TZ=) and legacy cron_expression.
  • Updates the scheduler’s ParseSchedule path to use the shared parser so next-fire and catch-up times respect the declared timezone.
  • Updates trigger validation to use the same parsing logic, and adds tests covering timezone behavior and invalid cron expressions.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
runs/service/trigger_service.go Routes trigger cron validation through the shared cron parser for consistent validation.
runs/service/task_service_test.go Adds table tests for cron validation across structured/legacy forms and invalid inputs.
runs/scheduler/core/scheduler_test.go Adds scheduler tests verifying next-fire and catch-up behavior in UTC, non-UTC, and at a DST boundary.
runs/scheduler/core/schedule_time.go Switches runtime schedule parsing to use the shared timezone-aware cron parser.
runs/schedule/cron.go Adds the shared cron parsing helper that applies CRON_TZ for structured cron schedules with timezone.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread runs/schedule/cron.go
Comment on lines +17 to +33
var expression string
switch value := schedule.GetExpression().(type) {
case *task.Schedule_CronExpression:
expression = value.CronExpression
case *task.Schedule_Cron:
expression = value.Cron.GetExpression()
if timezone := value.Cron.GetTimezone(); timezone != "" {
expression = fmt.Sprintf("CRON_TZ=%s %s", timezone, expression)
}
default:
return nil, nil
}

parsed, err := cron.ParseStandard(expression)
if err != nil {
return nil, fmt.Errorf("invalid cron expression %q: %w", expression, err)
}
if _, err := cron.ParseStandard(expr); err != nil {
if _, err := schedule.ParseCron(spec.GetSchedule()); err != nil {
return connect.NewError(connect.CodeInvalidArgument,
fmt.Errorf("trigger %q has invalid cron expression: %w", triggerName, err))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants