docs: document timestamp and time zone semantics - #25162
Conversation
Adds `docs/source/user-guide/sql/timestamps.md`, a new SQL Reference page that states DataFusion's timestamp/time zone model in one place, and links it from `data_types.md` and the SQL Reference toctree. The page covers: - the Arrow data model (`Timestamp(unit, Some(tz))` is an instant plus a display annotation; `Timestamp(unit, None)` is a wall clock with no instant) and how it maps to SQL's `TIMESTAMP` / `TIMESTAMP WITH TIME ZONE` - the one rule for conversions: naive -> zoned shifts, zoned -> zoned relabels, zoned -> naive yields the UTC wall clock - `AT TIME ZONE` expressed in terms of that rule - `datafusion.execution.time_zone`: what it does and does not affect, and the default-unset footgun that makes `::timestamptz` a naive type - a table of which date/time functions operate on the local wall clock and which on the UTC instant - daylight saving time, including the `INTERVAL '1 day'` vs `INTERVAL '24 hours'` distinction and the DST-boundary cast errors - recipes, including aggregating UTC data by local calendar day - known divergences from PostgreSQL, each linked to its issue Every SQL example was run against `datafusion-cli` on this commit and the pasted output is the real output. PostgreSQL claims were checked against PostgreSQL 17.11 and the `AT TIME ZONE` divergence against DuckDB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rewrite the prose of docs/source/user-guide/sql/timestamps.md to follow
ASD-STE100 Issue 8: approved vocabulary, one term per concept
("aware"/"naive timestamp"), active voice, simple tenses, sentences of
at most 25 words, instructions that start with the verb, and warnings
that give the instruction before the explanation.
No SQL block, result block, table data cell, URL, anchor target or
technical fact changes. The 33 code fences are byte-identical to the
previous commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Simplified Technical English pass defined "aware timestamp" and "naive timestamp" as the two terms for the page, but the PostgreSQL divergence table kept the older "zone-aware"/"zone-naive" wording, so the page carried two vocabularies for one concept. STE requires one term per concept. Rewords those table cells and one SQL comment. No factual claim changes, and every query and result block is byte-identical (verified block by block). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`to_local_time(t) AT TIME ZONE 'zone'` currently raises "Cannot cast timezone to different timezone" across an ambiguous hour, so the round trip cannot silently return a different instant. apache/arrow-rs#11038 removes that error, and once DataFusion picks it up the round trip moves by an hour with no error and no warning. PostgreSQL behaves the same way, so this is the intended trade rather than a regression -- but today the error is what stops users writing that round trip, so the docs need to say so before the behaviour changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Closing this for now. On reflection this page documents several things that are bugs or unresolved inconsistencies, and writing them down as reference material presents them as designed and intended. That is the wrong signal — a table showing that The findings that motivated those sections have been filed as issues instead, so nothing is lost:
#10344 stays open. Documenting timezone semantics is still worth doing — but it should describe behaviour we intend to keep, which means it wants writing after the issues above are resolved or explicitly accepted, not before. One piece of this is worth keeping regardless, and I will send it separately: |
Which issue does this PR close?
This is the last unchecked Documentation item in the EPIC
#8282.
Rationale for this change
DataFusion has a long tail of open timezone bugs:
#13212,
#12218,
#12892,
#23841,
#25095,
#18228,
#13962.
They look like seven unrelated bugs. They are the same confusion showing up at
seven different layers: when a timestamp gains or loses a time zone, is that a
relabel (keep the integer, change the annotation) or a shift (keep the
instant, change the wall clock)? Arrow's cast kernel says shift. Parts of
DataFusion — type coercion,
unwrap_cast_in_comparison,to_timestamp_*,::timestamptzwhendatafusion.execution.time_zoneis unset — assume relabel.That is already user-visible as a straight contradiction. On today's
main,with no session timezone set at all:
Three PRs are in flight right now, each settling one piece of this
independently:
timezone for timestamp subtraction / coercion
unwrap_cast_in_comparisondropping the timezone shifttimezone in
to_timestamp_*With nothing written down, those three can each settle it differently, and we
end up with a fourth reading. This page is the shared reference that lets them
agree: it states the rule once, in one place, derived from what the code
actually does today.
@kumarUjjawal @Ruchirtripathi @nevi-me — could you each check your change
against the "The one rule" section of the new page and say whether it moves
DataFusion toward or away from it? If any of you thinks the rule as written is
the wrong rule, that is exactly the discussion this PR is trying to start, and
it is much cheaper to have it here than after three PRs land.
What changes are included in this PR?
A new user-guide page,
docs/source/user-guide/sql/timestamps.md("Timestamps and Time Zones"), added to the SQL Reference toctree immediately
after
data_types.md.It is a new page rather than a section of
data_types.mdbecause the content iscross-cutting: it spans the type mapping, cast semantics, a session config
setting, the date/time function catalogue, DST, and query recipes.
data_types.mdis a terse type-mapping reference and would be swamped by it;operators.mdis the wrong home for a data-model discussion.Sections:
Timestamp(unit, Some(tz))is an instant plus a displayannotation;
Timestamp(unit, None)is a wall clock with no instant. How thatmaps to SQL
TIMESTAMP/TIMESTAMP WITH TIME ZONE, and where it does not.relabels; zoned → naive yields the UTC wall clock. Each with a
runnable example. Then
AT TIME ZONEexpressed in the same terms, and asubsection showing where DataFusion does not apply the rule consistently
today.
datafusion.execution.time_zonedoes anddoes not affect, with the default-unset footgun called out explicitly
(including the exact shape of
Grouping by utc timestamp turned into timezoned timestamp still uses utc for group #13962), and the type-unification
rules for mixed-zone values.
on the value's own local wall clock and which work on the UTC instant. The
date_trunc/date_bindisagreement is spelled out; both individually matchPostgreSQL, but the pair surprises everyone.
INTERVAL '1 day'vsINTERVAL '24 hours'(82800 / 86400 / 90000 seconds around the Denver transitions), and what
currently happens on ambiguous and nonexistent local times.
(this produces exactly the output requested in
Support
date_binon timestamps with timezone, properly accounting for Daylight Savings Time #10602), getting a zone's localwall clock, and round-tripping safely.
full URL.
Two smaller changes come with it:
docs/source/user-guide/sql/data_types.mdgains the missingTIMESTAMP WITH TIME ZONErow in the Date/Time table (it was simply absent),a note on precision, and a link to the new page.
docs/source/user-guide/sql/index.rstgains the toctree entry.What is the testing strategy for this PR?
Documentation only, so there is no code to test — but every claim on the page
was derived from behaviour rather than from reading the source, and every SQL
example is one that was actually run with its real output pasted in. Nothing on
the page is invented.
datafusion-clibuilt from this branch(DataFusion 55.0.0, arrow-rs 59.2.0), including the error messages.
(
docker run --rm -e POSTGRES_PASSWORD=pw postgres:17), not against thedocumentation:
date_trunc/date_bin/date_partbehaviour,INTERVAL '1 day'vs'24 hours'across both DST transitions,AT TIME ZONEon naive and aware inputs,tstz::timestamp, naive-vs-awarecomparison, and DST-boundary resolution.
AT TIME ZONE-on-an-aware-value divergence was additionally confirmedagainst DuckDB.
./ci/scripts/doc_prettier_check.shpasses.make html) was run and the new page renders; everycross-reference anchor it emits was checked to exist in the generated HTML.
The build's only warning is the pre-existing missing
deps.svg, whichbuild.shgenerates viacargo depgraphbeforemake html.A note on scope: the page documents today's behaviour, including behaviour
that is arguably wrong. Where an open issue proposes changing it, the page says
so and links the issue rather than documenting a future state. In particular the
AT TIME ZONE-on-an-aware-value rows carry an explicit note that#12218 may change them.
Are there any user-facing changes?
Yes — new user documentation. No API or behaviour changes.
🤖 Generated with Claude Code