Skip to content

feat: add timezone field support to on.schedule cron entries#22018

Merged
pelikhan merged 4 commits intomainfrom
copilot/update-compiler-support-timezone-schedules
Mar 20, 2026
Merged

feat: add timezone field support to on.schedule cron entries#22018
pelikhan merged 4 commits intomainfrom
copilot/update-compiler-support-timezone-schedules

Conversation

Copy link
Contributor

Copilot AI commented Mar 20, 2026

Summary

Adds compiler support for the optional timezone field in on.schedule items, as documented in the GitHub Actions workflow syntax.

Changes

  • pkg/workflow/schedule_preprocessing.go: In the schedule array processing loop, validate the optional timezone field (must be a string if present) and preserve it in the map so it passes through to the compiled YAML output unchanged.
  • pkg/workflow/schedule_preprocessing_test.go: Added TestScheduleTimezoneField with cases covering valid IANA timezone, UTC timezone, missing timezone (backward compat), multiple schedules with mixed timezones, and non-string timezone rejection.
  • pkg/parser/schemas/main_workflow_schema.json: Added timezone as an optional string property to the schedule item schema definition, with an IANA timezone description and a usage example.

Example

Workflows can now use timezone-aware schedule syntax:

on:
  schedule:
    - cron: '30 5 * * 1-5'
      timezone: "America/New_York"

Which compiles to the equivalent GitHub Actions YAML with the timezone field preserved.

Security Summary

No security vulnerabilities were introduced or discovered. CodeQL analysis found 0 alerts.


⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.

Copilot AI and others added 2 commits March 20, 2026 19:00
@pelikhan
Copy link
Contributor

@copilot update main_json_schema with timezone element

Copy link
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot update main_json_schema with timezone element

Done in 803971b. Added timezone as an optional string property to the schedule item definition in main_workflow_schema.json, with description and an example. The additionalProperties: false constraint is preserved so only cron and timezone are accepted.

@pelikhan pelikhan marked this pull request as ready for review March 20, 2026 20:25
Copilot AI review requested due to automatic review settings March 20, 2026 20:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds compiler + schema support for the optional timezone field on on.schedule entries so timezone-aware schedules can be validated and preserved into the compiled GitHub Actions YAML.

Changes:

  • Validate timezone (if present) is a string during schedule preprocessing and keep it in the schedule item map.
  • Add unit tests covering valid timezones, missing timezone (backward compatibility), mixed timezones, and invalid (non-string) timezone values.
  • Extend the workflow JSON schema to allow an optional timezone string on schedule items and add an example.

Reviewed changes

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

File Description
pkg/workflow/schedule_preprocessing.go Adds validation for optional timezone on schedule items during preprocessing.
pkg/workflow/schedule_preprocessing_test.go Adds test coverage for timezone validation/preservation behavior.
pkg/parser/schemas/main_workflow_schema.json Updates schema + examples to document/permit timezone on schedule items.

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

if tzValue, hasTimezone := itemMap["timezone"]; hasTimezone {
if _, ok := tzValue.(string); !ok {
return fmt.Errorf("schedule item %d 'timezone' field must be a string (IANA timezone, e.g. \"America/New_York\")", i)
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

When timezone is provided, the cron field can still contain your extended fuzzy syntax with explicit UTC offsets / timezone abbreviations (e.g. utc+9, PT). parser.ParseSchedule converts those to UTC hours, and then GitHub Actions will apply timezone again when interpreting the cron, resulting in an incorrect trigger time. Consider rejecting schedule strings that include explicit UTC offsets / TZ abbreviations when timezone is set (or define a clear precedence and adjust parsing accordingly).

Suggested change
}
}
// When a top-level 'timezone' is provided, disallow explicit timezone
// abbreviations or UTC offsets in the 'cron' field itself. Otherwise,
// the parser may convert the schedule to UTC and GitHub Actions will
// apply 'timezone' again, resulting in an incorrect trigger time.
lowerCron := strings.ToLower(cronStr)
for _, token := range strings.Fields(lowerCron) {
if strings.HasPrefix(token, "utc+") || strings.HasPrefix(token, "utc-") {
return fmt.Errorf("schedule item %d 'cron' field must not include explicit timezone or UTC offset when 'timezone' is set", i)
}
switch token {
case "pt", "pst", "pdt",
"et", "est,": // allow for common variants, see next case too
fallthrough
case "est", "edt",
"ct", "cst", "cdt",
"mt", "mst", "mdt":
return fmt.Errorf("schedule item %d 'cron' field must not include explicit timezone or UTC offset when 'timezone' is set", i)
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +1749 to +1836
tests := []struct {
name string
frontmatter map[string]any
expectedCron string
expectedTimezone string
expectedError bool
errorSubstring string
}{
{
name: "schedule with valid IANA timezone",
frontmatter: map[string]any{
"on": map[string]any{
"schedule": []any{
map[string]any{
"cron": "30 5 * * 1-5",
"timezone": "America/New_York",
},
},
},
},
expectedCron: "30 5 * * 1-5",
expectedTimezone: "America/New_York",
},
{
name: "schedule with UTC timezone",
frontmatter: map[string]any{
"on": map[string]any{
"schedule": []any{
map[string]any{
"cron": "0 2 * * *",
"timezone": "UTC",
},
},
},
},
expectedCron: "0 2 * * *",
expectedTimezone: "UTC",
},
{
name: "schedule without timezone (backward compatible)",
frontmatter: map[string]any{
"on": map[string]any{
"schedule": []any{
map[string]any{
"cron": "0 9 * * 1",
},
},
},
},
expectedCron: "0 9 * * 1",
expectedTimezone: "",
},
{
name: "multiple schedules with mixed timezone usage",
frontmatter: map[string]any{
"on": map[string]any{
"schedule": []any{
map[string]any{
"cron": "30 5 * * 1-4",
"timezone": "America/New_York",
},
map[string]any{
"cron": "30 17 * * 2,4",
"timezone": "Europe/London",
},
},
},
},
// Second schedule item checked separately in the test body below
expectedCron: "30 5 * * 1-4",
expectedTimezone: "America/New_York",
},
{
name: "timezone field must be a string - non-string rejected",
frontmatter: map[string]any{
"on": map[string]any{
"schedule": []any{
map[string]any{
"cron": "0 2 * * *",
"timezone": 123,
},
},
},
},
expectedError: true,
errorSubstring: "'timezone' field must be a string",
},
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

Add a negative test case that combines timezone with a schedule string containing an explicit UTC offset / timezone abbreviation (e.g. cron: "weekly on friday around 8 am PT" or "daily around 14:00 utc+9") to lock in the expected behavior (error or defined precedence). Without this, it’s easy to regress into double-applying time zone conversions.

Copilot uses AI. Check for mistakes.
Comment on lines 766 to +770
"description": "Cron expression using standard format (e.g., '0 9 * * 1') or fuzzy format (e.g., 'daily', 'daily around 14:00', 'daily between 9:00 and 17:00', 'weekly', 'weekly on monday', 'weekly on friday around 5pm', 'hourly', 'every 2h', 'every 10 minutes'). Fuzzy formats support: daily/weekly schedules with optional time windows, hourly intervals with scattered minutes, interval schedules (minimum 5 minutes), short duration units (m/h/d/w), and UTC timezone offsets (utc+N or utc+HH:MM)."
},
"timezone": {
"type": "string",
"description": "Optional IANA timezone string for timezone-aware scheduling (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo', 'UTC'). When set, the cron expression is interpreted in the specified timezone instead of UTC."
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The schema now documents both embedded UTC offset support in the cron string (via the fuzzy syntax) and the new timezone field. Consider clarifying that timezone should not be used together with embedded utc+/- offsets or TZ abbreviations in the cron value (or specify which takes precedence), since combining them can lead to incorrect scheduling after compilation.

Suggested change
"description": "Cron expression using standard format (e.g., '0 9 * * 1') or fuzzy format (e.g., 'daily', 'daily around 14:00', 'daily between 9:00 and 17:00', 'weekly', 'weekly on monday', 'weekly on friday around 5pm', 'hourly', 'every 2h', 'every 10 minutes'). Fuzzy formats support: daily/weekly schedules with optional time windows, hourly intervals with scattered minutes, interval schedules (minimum 5 minutes), short duration units (m/h/d/w), and UTC timezone offsets (utc+N or utc+HH:MM)."
},
"timezone": {
"type": "string",
"description": "Optional IANA timezone string for timezone-aware scheduling (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo', 'UTC'). When set, the cron expression is interpreted in the specified timezone instead of UTC."
"description": "Cron expression using standard format (e.g., '0 9 * * 1') or fuzzy format (e.g., 'daily', 'daily around 14:00', 'daily between 9:00 and 17:00', 'weekly', 'weekly on monday', 'weekly on friday around 5pm', 'hourly', 'every 2h', 'every 10 minutes'). Fuzzy formats support: daily/weekly schedules with optional time windows, hourly intervals with scattered minutes, interval schedules (minimum 5 minutes), short duration units (m/h/d/w), and UTC timezone offsets (utc+N or utc+HH:MM). When using a separate 'timezone' field, avoid including embedded 'utc+/-' offsets or timezone abbreviations in the cron string; if both are present, the 'timezone' field takes precedence for interpreting the schedule."
},
"timezone": {
"type": "string",
"description": "Optional IANA timezone string for timezone-aware scheduling (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo', 'UTC'). When set, the cron expression is interpreted in the specified timezone instead of UTC. Do not combine this field with embedded 'utc+/-' offsets or timezone abbreviations in the cron value; if such offsets/abbreviations are present, this 'timezone' field takes precedence."

Copilot uses AI. Check for mistakes.
@pelikhan pelikhan merged commit afe6b4d into main Mar 20, 2026
86 checks passed
@pelikhan pelikhan deleted the copilot/update-compiler-support-timezone-schedules branch March 20, 2026 20:40
github-actions bot added a commit that referenced this pull request Mar 21, 2026
Add documentation for the new `timezone` field on schedule items,
which allows cron expressions to be interpreted in a specific IANA
timezone instead of UTC (from #22018).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

3 participants