Why Your Automation Ran an Hour Off, and What It Missed
A scheduled automation drifts by an hour for two different reasons, and only one of them is a setting you can fix in five minutes. The harder failure is that a daylight saving day runs 23 or 25 hours, so a step that looks back exactly 24 hours on a fixed daily schedule double-processes an hour of records every March and skips an hour every November, with no error in the run log either time.
An automation that fires an hour off after a time change has a timezone set in the wrong place, and that takes about five minutes to fix. The expensive version of the same bug never shows up as a wrong time at all. A daylight saving day is 23 or 25 hours long, so a step that asks for the last 24 hours of records on a fixed daily schedule overlaps by an hour every March and leaves a one-hour hole every November. Nothing errors. The run log looks perfect both times.
Every article on this topic stops at the setting. The setting is the visible half. What follows is the other half, which is where the data actually goes missing.
There are four clocks in a scheduled automation, not one
The reason this is confusing is that "the timezone" is at least four separate settings that nobody configures at the same time.
| Clock | Who sets it | What a wrong value looks like |
|---|---|---|
| The trigger clock: when the job fires | Platform account setting, usually overridable per workflow | The 7 a.m. digest goes out at 2 a.m., or shifts an hour twice a year |
| The expression clock: what "today" means inside the flow | The workflow timezone for date helpers, the container's system clock for native date functions | The daily report is correct at 9 a.m. and covers the wrong day at 9 p.m. |
| The source clock: what the API you query stamps and filters on | The vendor, and it is almost never your timezone | Off-by-one-day filters, records that appear a day late |
| The destination clock: what the recipient reads | Whatever you formatted the output in | A confirmation text telling a customer 3 p.m. when the appointment is at 6 p.m. |
Fixing the first one does not touch the other three. That is worth stating plainly, because the standard advice is to correct the account timezone and consider the matter closed.
Here is a test for the second clock that takes one execution. Output the current time twice in the same run, once with the platform's date helper and once with the raw language function, and compare them. In n8n that is $now.toISO() in an expression against new Date().toISOString() in a Code node. If they disagree by your UTC offset, you have two clocks in one workflow and the one you never configured is deciding what "yesterday" means.
The setting is the easy half
Get it out of the way first. Three platforms, three defaults, and the defaults are the problem.
n8n resolves the schedule against the workflow timezone if one is set, and the instance timezone if not. The self-hosted default is America/New_York regardless of where your server is, changed with the GENERIC_TIMEZONE environment variable. On n8n Cloud the instance timezone is guessed from the owner's signup and falls back to GMT.
Zapier sets new accounts to UTC by default, which is why so many Zaps run at a sensible-looking time that is quietly five hours off, and a Zap uses the account timezone unless you override it in that Zap's own settings. Make schedules against the account timezone and shows you the next run in local time while evaluating internally in UTC, which is exactly the combination that makes an hour of drift look like a display bug.
The rule we apply: pin the timezone at the workflow level, explicitly, on every scheduled flow, even when the account default is already right. An inherited setting is a setting somebody else can change.
A daylight saving day is not 24 hours long
This is the part that costs money.
Take a flow that runs daily at 6:00 a.m. in America/New_York and asks your CRM for everything created in the last 24 hours. It has worked for two years. Then the clocks move.
On March 8, 2026, the spring-forward date, the 6:00 a.m. run happens only 23 real hours after the previous one, because an hour of that night did not exist. The flow still looks back a full 24 hours, so it reaches an hour further back than the last run already covered. One hour of records gets processed twice.
On November 1, 2026, the fall-back date, the 6:00 a.m. run happens 25 real hours after the previous one. The flow looks back 24. Everything created in that leftover hour, 6:00 to 7:00 a.m. the previous morning, is never fetched by any run, ever.
The mismatch is between how the schedule advances and how the window is computed. A cron schedule at a fixed local time advances by calendar days, which are 23, 24, or 25 hours long. A lookback of 24 hours advances by exactly 24. Two different kinds of arithmetic, and they only agree 363 days a year.
Luxon, the date library behind n8n expressions, makes the distinction explicit rather than hiding it. Adding days, weeks, months, or years is calendar math and adjusts for DST. Adding hours, minutes, or seconds is exact math and does not. Their own example is a March 11 timestamp at 10 a.m.: plus({days: 1}) returns 10 a.m. the next day, and plus({hours: 24}) returns 11 a.m. Same starting point, two correct answers, one of which is wrong for your schedule.
So the rule is to match the math to the schedule:
| Schedule shape | Real hours between runs on a switch day | Lookback that matches |
|---|---|---|
| Cron at a fixed local time in a DST zone | 23 or 25 | Calendar math, minus({days: 1}) |
| Cron at a fixed UTC time, or a zone with no DST | 24 | Exact math, minus({hours: 24}) |
| Interval-based, every 24 hours | 24, but the wall-clock time drifts | Exact math, minus({hours: 24}) |
| Every 15 minutes, or anything sub-hourly | Not applicable | Neither, use a watermark |
AWS documents that split for its own scheduler, which is useful confirmation that it is a real distinction and not a quirk of one tool. A cron-based EventBridge schedule in America/New_York shifts with the zone, while a rate-based schedule using rate(1 days) is evaluated 24 hours after the last invocation whether the day was 23 hours or 25.
The better answer is to stop doing the arithmetic. Store the end of the last successful run as a UTC timestamp, query for records at or after that value and before the current run's start, and write the new watermark only after the downstream write has succeeded. The window then picks up exactly where the previous one stopped, and the length of the day stops being a variable in your pipeline. It also makes the duplicate case survivable, which is the same discipline that keeps an automation from creating duplicates in every other retry scenario.
The run that never happened, and never errored
There is a second failure that is not about windows at all. Some runs simply do not occur.
AWS states the behavior directly for EventBridge Scheduler: when time shifts forward in the spring, if a cron expression falls on a non-existent date and time, the invocation is skipped. Their worked example is a schedule set to 2:30 a.m. in America/Los_Angeles, which is skipped entirely on the spring-forward date and resumes normally the next day. Going the other direction, the same schedule runs once and does not repeat when 2:59 a.m. rolls back to 2:00 a.m.
Two consequences for anything you have scheduled between 1:00 and 3:00 a.m. local time, which is where most nightly jobs live because that is when nobody is using the system.
First, once a year that job does not run. Not late, not failed. Absent. There is no failed execution, no error notification, and nothing for error-based monitoring to catch, which is precisely why this pattern needs an alert that fires on the absence of a run rather than on a run that went wrong. A nightly reconciliation that skips a day leaves a gap you find in a quarterly report.
Second, the fall-back case depends on a platform behavior you probably have not read. AWS documents that it will not repeat the invocation. If your platform does not say so in writing, assume the job can fire twice in the same night and make the step idempotent. This is the same class of quiet breakage as the other ways automations fail without telling you, with the difference that the trigger date is on the calendar a year in advance.
The cheap mitigation, and the one we default to: do not schedule anything at 1:00 to 2:59 a.m. local. Move it to 3:15 a.m. Nothing else changes, the ambiguous window is avoided in both directions, and you have removed a class of annual bug with a two-minute edit.
Where to start: the 30 minute time audit
Open your automation platform and list every flow with a schedule trigger. For each one, write down four things: the timezone the trigger resolves against, whether it is set on the workflow or inherited, the local hour it fires, and whether any step inside it computes a relative time window.
Then sort. Anything firing between 1:00 and 3:00 a.m. local gets moved to 3:15. Anything with a relative lookback gets its math matched to its schedule, or better, a stored watermark. Anything inheriting its timezone from the account gets it pinned explicitly. The two US dates to have on the calendar are the second Sunday in March and the first Sunday in November, which in 2026 are March 8 and November 1, and the useful habit is checking the following Monday's run counts against the previous week rather than waiting for someone to notice a number is off.
Most operators find one or two flows where the window arithmetic has been quietly wrong for a year. If you want the schedules and the data windows mapped together across a stack you did not build, tell us what you are running and we will go through it with you.
Frequently Asked Questions
SOURCES & CITATIONS
- Schedule types in EventBridge Scheduler — Amazon Web Serviceshttps://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html
- Luxon documentation: Math — Luxonhttps://github.com/moment/luxon/blob/master/docs/math.md
- Schedule Trigger node documentation — n8n Docshttps://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.scheduletrigger/
- Zap dates or times are incorrect — Zapier Help Centerhttps://help.zapier.com/hc/en-us/articles/8496215472653-Zap-dates-or-times-are-incorrect
About Alexey Yushkin
Alexey is the founder of GENERAL INFORMATICS LLC. He designs and ships AI and automation systems for businesses and operators across the US.
Related reading
Want this kind of system in your business?
We build practical AI and automation systems for operators. Send us your current workflow and we will show you what to automate first.
Request a Workflow Review