Skip to content

feat: make the working timezone configurable end-to-end - #20

Merged
AlexeyShalaev merged 1 commit into
masterfrom
feat/configurable-timezone
Aug 27, 2026
Merged

feat: make the working timezone configurable end-to-end#20
AlexeyShalaev merged 1 commit into
masterfrom
feat/configurable-timezone

Conversation

@AlexeyShalaev

Copy link
Copy Markdown
Contributor

Summary

Until now the timezone story had three independent layers: period computation hardcoded UTC, DDL had its own ddl_timezone knob, and pruning hardcoded UTC again — so passing ddl_timezone="Europe/Moscow" silently shifted real partition bounds relative to their names and to the pruning cutoff. This PR threads one timezone through all three layers. Defaults are bit-identical to the previous behavior — the entire pre-existing suite passes unchanged.

Design decisions

  • Form of the knob: tz: tzinfo = datetime.UTC on every calculator (BasePeriodCalculator.__init__, passthrough in get_period_calculator). Only datetime.UTC and keyed zoneinfo.ZoneInfo are accepted (utils.timezone_name raises otherwise) — the zone must have an IANA name usable in SET LOCAL TIME ZONE, which rules out fixed offsets and unnamed tzinfo objects. New calculator properties tz / timezone_name.
  • A↔B guard — error, not inference: PartitionLifecycleService.__init__ compares calculator.timezone_name with the repository's new ddl_timezone property (case-insensitive) and raises ValueError on mismatch. Silent inference of ddl_timezone from the calculator would have required distinguishing "defaulted" from "explicitly UTC" at the repo constructor; a loud construction-time error achieves the invariant (silent misalignment is impossible) without sentinel-typed public defaults. Custom repos/calculators without timezone metadata are not checked.
  • ddl_timezone=None (trust the session) is kept: non-UTC calculator + None logs a warning — the library cannot verify alignment in that mode; pruning still uses the calculator's zone for naive values. Documented honestly in Advanced.
  • A↔C: pruning services take the zone from the calculator (getattr(calculator, "tz", UTC) — custom calculators keep the historical UTC interpretation) and interpret naive catalog boundaries in it before comparing UTC instants; aware boundaries (the normal case for timestamptz keys) are converted exactly as before.
  • Hour + DST — UTC-only (option a): HourPeriodCalculator raises for any non-UTC tz; in a DST zone a local hour can repeat or vanish, making table__YYYY_MM_DD_HH names ambiguous. Covered by tests and docs.
  • Period stays a naive value object; Period.to_datetime() keeps its UTC anchor — inside pruning it is only a per-granularity sort key, where any fixed anchor gives the same order.
  • Out of scope (follow-up idea): emitting explicit-offset literals for all granularities as an additional defense would make boundaries self-contained regardless of session TZ — deliberately not done here per the task's scope note.

Test plan

  • make check — ruff + mypy pass
  • uv run --extra pydantic-settings pytest -m unit — 686 passed (+50: per-granularity UTC-vs-Moscow boundary cases under freezegun, tz property/validation, hour DST guard, alignment guard incl. case-insensitivity / None-warning / custom-implementation skip, pruning naive-boundary interpretation)
  • uv run pytest tests/integration --collect-only — 102 collected (+6: Moscow calculator under an unrelated America/Los_Angeles session produces 21:00:00+00 Moscow-midnight bounds in relpartbound; pruning selects the correct set through Moscow-aligned stack; mismatched pair fails construction)
  • Existing suite green with zero edits (regression invariant)
  • CI integration job (testcontainers PostgreSQL 17) — integration tests were not run locally (no Docker)

Calculators accept tz (datetime.UTC default, or a keyed ZoneInfo): the
current period, partition names, and the meaning of naive boundary
literals all follow it. PartitionLifecycleService refuses a calculator/
ddl_timezone mismatch so names and real bounds can no longer drift apart
silently; ddl_timezone=None logs a warning with a non-UTC calculator.
Pruning interprets naive catalog boundaries in the calculator's timezone.
HourPeriodCalculator is UTC-only: local hour names are ambiguous under
DST. Defaults are bit-identical to the previous behavior.
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@AlexeyShalaev
AlexeyShalaev merged commit bce7a77 into master Aug 27, 2026
7 checks passed
@AlexeyShalaev
AlexeyShalaev deleted the feat/configurable-timezone branch August 27, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant