Timezones are the bane of every software developer’s existence. Between leap seconds, UTC offsets, and Daylight Saving Time (DST), scheduling logic is surprisingly fragile.
When running background jobs, timezone bugs can manifest in confusing ways: jobs running twice, jobs being skipped entirely, or emails sending to customers at 3 AM instead of 8 AM.
Here is how to design timezone-aware background jobs that run exactly when expected.
1. Always Run Servers on UTC
The first rule of backend architecture is that all server clocks, databases, and logs must run on Coordinated Universal Time (UTC).
If your servers run on local time (e.g., Eastern Time), Daylight Saving Time transitions will cause severe issues twice a year:
- In Autumn (Fall): The clock rolls back (e.g., from 2:00 AM to 1:00 AM). Any job scheduled between 1:00 AM and 2:00 AM will execute twice.
- In Spring: The clock skips ahead (e.g., from 1:00 AM to 2:00 AM). Any job scheduled during that skipped hour will never run at all.
By forcing UTC, the clock always ticks forward sequentially with no shifts.
2. Store Schedules with Timezone Offsets
If your SaaS application allows users to schedule reports or alerts in their own timezone, you cannot simply convert their request to UTC once and save it.
If a user wants a report daily at 9:00 AM in London:
- During Winter (GMT), 9:00 AM is 9:00 AM UTC.
- During Summer (BST), 9:00 AM is 8:00 AM UTC.
To solve this:
- Store the user's localized time (e.g.,
09:00) and their timezone identifier (e.g.,Europe/London). - Use a timezone-aware scheduler (like CronRabbit or a library like
cron-parserwithmoment-timezone) to dynamically calculate the next UTC execution time based on the timezone definition.
3. Implement Strict Idempotency
Even with the best configuration, unexpected server reboots or system updates can cause scheduler restarts. Always ensure your background workers are idempotent (i.e., running them multiple times yields the same result) so that an accidental double-run doesn't damage your system.
