Skip to content

Handle DST gaps in make_aware - #10324

Merged
auvipy merged 5 commits into
celery:mainfrom
puneetdixit200:fix/make-aware-dst-gap
Jun 8, 2026
Merged

auvipy merged 5 commits into
celery:mainfrom
puneetdixit200:fix/make-aware-dst-gap

Conversation

@puneetdixit200

Copy link
Copy Markdown
Contributor

Description

make_aware() currently treats spring-forward gap times as ambiguous for ZoneInfo timezones, so a nonexistent local time like 2024-03-10 02:30 in US/Eastern keeps the pre-transition offset instead of moving to the first real wall time after the gap.

This updates the helper to detect imaginary datetimes before ambiguous datetimes and resolve them with dateutil.tz.resolve_imaginary(). The existing ambiguous-time path remains unchanged for real fall-back times and custom timezone objects.

Fixes #10322.

Tests

  • .venv\Scripts\python -m pytest t\unit\utils\test_time.py::test_make_aware::test_tz_when_zoneinfo_and_time_does_not_exist -q
  • .venv\Scripts\python -m pytest t\unit\utils\test_time.py::test_make_aware -q
  • .venv\Scripts\python -m pytest t\unit\utils\test_time.py t\unit\app\test_schedules.py -q
  • .venv\Scripts\python -m flake8 celery\utils\time.py t\unit\utils\test_time.py

Note: I also tried .venv\Scripts\python -m pytest t\unit -q, but the local minimal environment is missing optional dependencies used by unrelated tests (pydantic, Azure/Google/Boto libraries, DNS, cryptography, Redis provider pieces), so collection stops before running the full suite.

@auvipy auvipy added this to the 5.7.0 milestone May 23, 2026
@codecov

codecov Bot commented May 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.31%. Comparing base (bf1cf69) to head (64fdbaf).
⚠️ Report is 134 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #10324      +/-   ##
==========================================
+ Coverage   88.30%   88.31%   +0.01%     
==========================================
  Files         153      153              
  Lines       19734    19743       +9     
  Branches     2286     2288       +2     
==========================================
+ Hits        17427    17437      +10     
  Misses       2008     2008              
+ Partials      299      298       -1     
Flag Coverage Δ
unittests 88.29% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@auvipy auvipy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please cross check the test failures

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes celery.utils.time.make_aware() for ZoneInfo timezones during spring-forward DST gaps by detecting non-existent (“imaginary”) local datetimes and normalizing them via dateutil.tz.resolve_imaginary(), while preserving the existing fall-back ambiguous-time handling. This addresses the incorrect pre-transition UTC offset behavior reported in #10322 and adds a regression test for the gap case.

Changes:

  • Add _is_imaginary() and update make_aware() to resolve non-existent local times before ambiguous-time resolution.
  • Add a unit test covering a known US/Eastern spring-forward gap datetime (2024-03-10 02:30).

Reviewed changes

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

File Description
celery/utils/time.py Adds imaginary-time detection and uses resolve_imaginary() in make_aware() before ambiguity handling.
t/unit/utils/test_time.py Adds a regression test ensuring spring-forward gap times are advanced and get the correct UTC offset.

Comment thread celery/utils/time.py Outdated
Comment thread celery/utils/time.py Outdated
@puneetdixit200
puneetdixit200 force-pushed the fix/make-aware-dst-gap branch from fe7e0e0 to 011d644 Compare May 23, 2026 18:33
@puneetdixit200

Copy link
Copy Markdown
Contributor Author

I cross-checked the reported failures. The two Codecov failures were in t/unit/app/test_preload_cli.py and came from Click formatting the error as No such option '--ini' instead of No such option: --ini.

Current head 011d644 updates that assertion and also addresses the inline feedback on the make_aware() helper. I reran the focused time tests and the preload CLI failure locally before pushing.

@puneetdixit200

Copy link
Copy Markdown
Contributor Author

Pushed af8a97d to cover the Codecov gap in the new _is_imaginary() helper. The added test exercises the defensive path for timezones that cannot detect ambiguous/imaginary datetimes.

Local verification:

  • python -m coverage run --branch --source=celery -m pytest t/unit/utils/test_time.py -q (60 passed)
  • python -m coverage report -m celery/utils/time.py (no PR-diff lines listed as missing)
  • python -m pytest t/unit/utils/test_time.py::test_make_aware -q (5 passed)
  • python -m flake8 celery/utils/time.py t/unit/utils/test_time.py
  • git diff --check

@puneetdixit200
puneetdixit200 force-pushed the fix/make-aware-dst-gap branch from af8a97d to 14731b6 Compare May 26, 2026 09:37
@puneetdixit200

Copy link
Copy Markdown
Contributor Author

Pushed a test-only follow-up in 14731b6 to cover the remaining make_aware imaginary-time fallback branches reported by Codecov.

Verified locally:

  • .venv/bin/python -m pytest t/unit/utils/test_time.py::test_make_aware -q --cov=celery.utils.time --cov-report=term-missing:skip-covered
  • .venv/bin/python -m pytest t/unit/utils/test_time.py t/unit/app/test_schedules.py -q
  • .venv/bin/python -m pytest t/unit/app/test_preload_cli.py::test_preload_options -q
  • .venv/bin/python -m flake8 celery/utils/time.py t/unit/utils/test_time.py
  • git diff --check

The new GitHub run has lint/scan/smoke/pre-commit passing so far; the unit matrix is still running.

@puneetdixit200

Copy link
Copy Markdown
Contributor Author

Coverage follow-up is on the branch now. The _is_imaginary() fallback branches are covered by 14731b674, and pre-commit.ci formatted the import in 64fdbaf68.

Local verification on the current remote head:

uv run --python 3.14 --with-editable . --with pytest --with coverage python -m coverage run --branch --source=celery -m pytest -q t/unit/utils/test_time.py::test_make_aware
# 6 passed

uv run --python 3.14 --with-editable . --with pytest --with coverage python -m coverage report -m celery/utils/time.py
# new fallback lines are no longer reported missing

uv run --python 3.14 --with-editable . --with pytest python -m pytest -q t/unit/utils/test_time.py
# 61 passed, existing parse_iso8601 deprecation warnings only

uv run --python 3.14 --with flake8 python -m flake8 t/unit/utils/test_time.py
git diff --check

GitHub now has Codecov patch/project green and the unit shards green. The larger integration/smoke matrix is still running.

@puneetdixit200

Copy link
Copy Markdown
Contributor Author

Follow-up: current head 64fdbaf682383e9d4cef8d61e43be561a9521b44 is green now. codecov/patch and codecov/project pass, and the unit, lint, docs, smoke, and expanded integration/smoke rows have completed without red checks.

@auvipy

auvipy commented May 27, 2026

Copy link
Copy Markdown
Member

as we also have a package named django-celery-beat which rely on this part of celery, we have to cross check if any adjustment also needed there or not

@puneetdixit200

Copy link
Copy Markdown
Contributor Author

Cross-checked django-celery-beat against this change.

  • Its direct make_aware() in django_celery_beat/utils.py is a local Django helper, not celery.utils.time.make_aware().
  • The Celery helper imports I found are maybe_make_aware() in clockedschedule.py and schedulers.py. Both call it with the default naive_as_utc=True; on this branch that path first coerces naive datetimes to UTC via to_utc() and then localizes, so it does not hit the DST-gap local-time path changed here.
  • schedulers.py already documents that distinction: make_aware assumes the Django project timezone for naive datetimes, while maybe_make_aware assumes UTC.

So I do not see a required django-celery-beat adjustment for this Celery patch. The behavior change remains scoped to callers that pass a local timezone into Celery's make_aware() path, or call maybe_make_aware(..., naive_as_utc=False) around imaginary local datetimes.

@auvipy
auvipy merged commit c30f42a into celery:main Jun 8, 2026
338 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants