Skip to content

Fix drifting data intervals for monthly/yearly schedules with catchup disabled#69143

Merged
Lee-W merged 5 commits into
apache:mainfrom
shahar1:fix-monthly-yearly-catchup-data-interval-drift
Jun 30, 2026
Merged

Fix drifting data intervals for monthly/yearly schedules with catchup disabled#69143
Lee-W merged 5 commits into
apache:mainfrom
shahar1:fix-monthly-yearly-catchup-data-interval-drift

Conversation

@shahar1

@shahar1 shahar1 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Human Summary

We encountered this issue in my work when we scheduled a Dag on a past start_date with catchup=False and schedule of relativedelta(months=1). In the specific case, the data interval of the first run drifted in about 5 days that it should be (e..g, 2026-05-01 became 2025-05-06). The problem was that in these case it calculates the date over multiplications of fixed 30 days since the Unix epoch (1970-01-01), which naturally leads to a drift due to different number of days in a month since then (there's an equivalent issue for using years due to leap years). This PR should fix it.

AI Summary

Click here Monthly and yearly schedules (`relativedelta(months=...)` / `relativedelta(years=...)`) produced mis-aligned, drifting data intervals when `catchup=False`.

DeltaDataIntervalTimetable._skip_to_latest (only reached on the catchup=False path) rounded "now" to a fixed 30-day / 365-day grid anchored at the Unix epoch. Calendar months and years are not constant in length, so the first data interval after a Dag is unpaused never aligned to the Dag's start_date, and the chosen day-of-month walked backwards over time (e.g. 06-06 → 07-06 → 08-05 → 09-04). The logical date a monthly Dag received therefore depended on when it happened to be unpaused rather than being a stable function of its schedule.

The fix anchors calendar-based deltas on start_date and advances one period at a time, so the boundaries match what catchup=True produces (including relativedelta's path-dependent day-clamping, e.g. Jan 31 → Feb 28 → Mar 28). timedelta and fixed-unit relativedelta schedules are untouched — they keep the existing O(1) epoch rounding, which is correct for fixed deltas.

Added unit tests for monthly/yearly alignment, the no-start_date case, and day-clamping; all fail on the previous code and pass with the fix. The existing hourly timedelta/relativedelta and catchup=True tests guard the unchanged path.


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 4.8)

Generated-by: Claude Code (Opus 4.8) following the guidelines

… disabled

When a Dag scheduled with relativedelta(months=...) or relativedelta(years=...)
runs with catchup=False, the first data interval after the Dag is unpaused was
snapped to a fixed 30-day (or 365-day) grid measured from the Unix epoch. Real
calendar months and years vary in length, so the resulting interval never
aligned to the Dag's start_date and its day-of-month walked backwards over time.
The logical date a monthly Dag received therefore depended on when it happened
to be unpaused rather than being a stable function of its schedule.
@shahar1 shahar1 requested review from Lee-W and uranusjr as code owners June 29, 2026 21:10
@shahar1 shahar1 requested a review from hussein-awala June 29, 2026 21:21
Comment thread airflow-core/src/airflow/timetables/interval.py Outdated
@shahar1 shahar1 added backport-to-v3-3-test Backport to v3-3-test area:core type:bug-fix Changelog: Bug Fixes labels Jun 30, 2026
@shahar1 shahar1 added this to the Airflow 3.3.0 milestone Jun 30, 2026
Move the calendar-delta stepping next to the fixed-delta rounding so both
schedule kinds resolve their boundary through a single helper, leaving
_skip_to_latest a single expression. No behavior change.
@shahar1 shahar1 requested a review from uranusjr June 30, 2026 05:18
Comment thread airflow-core/src/airflow/timetables/interval.py Outdated
Comment thread airflow-core/src/airflow/timetables/interval.py
Comment thread airflow-core/tests/unit/timetables/test_interval_timetable.py Outdated
@Lee-W Lee-W merged commit 73700f0 into apache:main Jun 30, 2026
77 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-3-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
v3-3-test PR Link

@shahar1 shahar1 deleted the fix-monthly-yearly-catchup-data-interval-drift branch July 1, 2026 05:39
uranusjr pushed a commit that referenced this pull request Jul 1, 2026
…with catchup disabled (#69143) (#69189)

Co-authored-by: Shahar Epstein <60007259+shahar1@users.noreply.github.com>
vatsrahul1001 pushed a commit that referenced this pull request Jul 3, 2026
…with catchup disabled (#69143) (#69189)

Co-authored-by: Shahar Epstein <60007259+shahar1@users.noreply.github.com>
vatsrahul1001 pushed a commit that referenced this pull request Jul 7, 2026
…with catchup disabled (#69143) (#69189)

Co-authored-by: Shahar Epstein <60007259+shahar1@users.noreply.github.com>
vatsrahul1001 pushed a commit that referenced this pull request Jul 9, 2026
…with catchup disabled (#69143) (#69189)

Co-authored-by: Shahar Epstein <60007259+shahar1@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core backport-to-v3-3-test Backport to v3-3-test type:bug-fix Changelog: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants