Time-Zone-Safe Scheduling Pack
An offset is a number. A time zone is a rule set for a place.
-08:00 cannot tell you that Los Angeles moves to -07:00 in summer. If a reminder should stay at 9:00 in the user's local time, store an IANA zone such as America/Los_Angeles, not the offset that happened to be correct when the account was created.
Model the two kinds of time
An instant is one point on the global timeline. Store it in UTC for an event that already happened: payment received, message sent, deployment completed.
A local schedule is a wall-clock intention plus a zone: every weekday at 9:00 in America/New_York. Do not precompute years of future UTC instants and forget the local rule. Time-zone law and tzdata can change.
Store:
- the local date and time rule;
- the IANA zone name;
- recurrence details;
- the policy for daylight-saving gaps and overlaps;
- computed instants for near-term execution, with the tzdata version if useful.
Gaps and overlaps
During the spring transition, some local times do not exist. During the autumn transition, some happen twice. Your product must choose rather than letting a date library choose invisibly.
For a nonexistent 2:30, you might reject it, move to the next valid time, or ask the user. For a repeated 1:30, choose the earlier or later occurrence and show the selected offset. Scheduling money, medicine, or access changes may require a stricter policy than sending a digest.
Recurring work
Calculate the next occurrence from the local rule and current time-zone data. Keep historical instants unchanged when tzdata updates, but recalculate future occurrences that have not executed. Make the scheduler idempotent so recalculation cannot send the same job twice.
Verification checklist
- Test the spring gap in every supported zone with daylight saving.
- Test both occurrences in the autumn overlap.
- Prove a 9:00 local recurrence remains at 9:00 when its UTC offset changes.
- Test half-hour and quarter-hour zones, not only whole-hour examples.
- Update the tzdata fixture and prove future occurrences recalculate while history does not.
- Cross a date boundary between the user zone and UTC.
- Retry one scheduled execution and prove it runs once.
The starter accepts stored timestamps, an IANA zone, local and recurrence rules, transition fixtures, and a tzdata policy. It returns the time model, persistence rule, gap/overlap decisions, recurrence calculation, and transition tests. Dry-run schedules and sends nothing.
Download the runnable pack
- Complete workflow pack ZIP
- Importable n8n workflow
- GitHub validation workflow
- GitHub manual run workflow
- Docker Compose file
- Fictional sample payload
- Expected safe dry-run result
Comment TIMEZONE for the pack. A place stays meaningful after its offset expires.