feat: make the working timezone configurable end-to-end - #20
Merged
Conversation
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 Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Until now the timezone story had three independent layers: period computation hardcoded UTC, DDL had its own
ddl_timezoneknob, and pruning hardcoded UTC again — so passingddl_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
tz: tzinfo = datetime.UTCon every calculator (BasePeriodCalculator.__init__, passthrough inget_period_calculator). Onlydatetime.UTCand keyedzoneinfo.ZoneInfoare accepted (utils.timezone_nameraises otherwise) — the zone must have an IANA name usable inSET LOCAL TIME ZONE, which rules out fixed offsets and unnamed tzinfo objects. New calculator propertiestz/timezone_name.PartitionLifecycleService.__init__comparescalculator.timezone_namewith the repository's newddl_timezoneproperty (case-insensitive) and raisesValueErroron mismatch. Silent inference ofddl_timezonefrom 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 +Nonelogs a warning — the library cannot verify alignment in that mode; pruning still uses the calculator's zone for naive values. Documented honestly in Advanced.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 fortimestamptzkeys) are converted exactly as before.HourPeriodCalculatorraises for any non-UTC tz; in a DST zone a local hour can repeat or vanish, makingtable__YYYY_MM_DD_HHnames ambiguous. Covered by tests and docs.Periodstays 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.Test plan
make check— ruff + mypy passuv 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 unrelatedAmerica/Los_Angelessession produces21:00:00+00Moscow-midnight bounds inrelpartbound; pruning selects the correct set through Moscow-aligned stack; mismatched pair fails construction)