adriangb opened a new issue, #25166:
URL: https://github.com/apache/datafusion/issues/25166

   ### Describe the bug
   
   Two related type-level problems with `TIMESTAMP WITH TIME ZONE`. They share 
a root cause and I think they should be decided together.
   
   **1. `TIMESTAMP WITH TIME ZONE` can resolve to a timezone-*naive* Arrow 
type.**
   
   With the default configuration (`datafusion.execution.time_zone` unset):
   
   ```sql
   SELECT arrow_typeof('2024-01-01T12:00:00Z'::timestamptz) AS type,
          '2024-01-01T12:00:00Z'::timestamptz AS value;
   +---------------+---------------------+
   | type          | value               |
   +---------------+---------------------+
   | Timestamp(ns) | 2024-01-01T12:00:00 |
   +---------------+---------------------+
   ```
   
   The `Z` is accepted and then discarded. A SQL type whose name is "with time 
zone" produced an Arrow type with no time zone.
   
   **2. Casting an already timezone-aware value to `timestamptz` *overwrites* 
its timezone.**
   
   ```sql
   SET datafusion.execution.time_zone = 'UTC';
   SELECT arrow_typeof((t AT TIME ZONE 'Europe/Brussels')::timestamptz) FROM …;
   -- Timestamp(ns, "UTC")   -- the Brussels annotation is gone
   ```
   
   This is the root cause of #13962: `date_trunc('day', (t AT TIME ZONE 
'Europe/Brussels')::timestamptz)` groups by UTC day while naming Brussels. 
Remove the `::timestamptz` and the same query is correct. It is also the same 
defect family as #23841 (`to_timestamp` discarding an aware input's timezone).
   
   ### To Reproduce
   
   Both snippets above, on DataFusion 55.0.0 (`da89c7c85b`).
   
   ### Expected behavior
   
   Two invariants, which I think are worth stating explicitly whatever the fix:
   
   1. **`TIMESTAMP WITH TIME ZONE` never resolves to `Timestamp(_, None)`.** 
When the session time zone is unset it should fall back to a fixed zone rather 
than to naive.
   2. **`CAST(x AS TIMESTAMPTZ)` where `x` is already aware preserves `x`'s own 
timezone.**
   
   ### Additional context
   
   **This is a traceable side effect, not a deliberate design.** The default 
changed from `"+00:00"` to `None` in #18359 (DataFusion 51.0.0). That change 
was deliberate and well reasoned — it followed #18017 making 
`now()`/`current_date()`/`current_time()` read the session timezone, and #18204 
/ #18081 proposed making the config an `Option`.
   
   But the whole of its effect on the SQL type system was this edit in 
`datafusion/sql/src/planner.rs`:
   
   ```diff
   -  Some(self.context_provider.options().execution.time_zone.clone())
   +  self.context_provider.options().execution.time_zone.clone()
   ```
   
   a mechanical unwrap because the field's type changed. The comment two lines 
above it is still in the tree today and still says:
   
   ```rust
   // OUTPUT: [ArrowDataType] Timestamp<TimeUnit, Some(Time Zone)>
   ```
   
   Neither the PR body, nor any of the three driver issues, nor the 
upgrade-guide note mentions `timestamptz` or `TIMESTAMP WITH TIME ZONE` — the 
note frames the change purely as *"to better support using the default timezone 
in scalar UDF functions such as `now`, `current_date`, `current_time`, and 
`to_timestamp`"*. The type-system consequence was never discussed.
   
   **PostgreSQL and DuckDB (measured, PG 17.11 / DuckDB 1.5.2)** both default 
`TimeZone` to the machine's zone, never unset, so `timestamptz` is always aware 
in both. Their type is also invariant to the session zone — only rendering 
changes:
   
   ```
   PG      SET TimeZone='UTC';              pg_typeof -> timestamp with time 
zone,  12:00:00+00
           SET TimeZone='Europe/Brussels';  pg_typeof -> timestamp with time 
zone,  13:00:00+01
   DuckDB  same in both
   ```
   
   So invariant 1 has two reference implementations. **Invariant 2 does not**, 
and this is worth being explicit about: neither system can represent a 
per-value timezone at all, so neither can arbitrate. Worse, copying PostgreSQL 
literally gives you the current behaviour — in PG, `tstz::timestamptz` renders 
in the *session* zone because the value never carried one. Invariant 2 has to 
be argued on DataFusion's own terms: Arrow puts the zone in the value, so an 
operation not asked to change it shouldn't. `AT TIME ZONE 'Z'` already means 
"convert to Z" and `arrow_cast` already means "give me exactly this Arrow 
type", so `::timestamptz` does not need to be a third way to change a timezone.
   
   **Note that simply changing the default would not fix #13962.** With the 
default set to UTC, invariant 1 holds but the cast still relabels Brussels to 
UTC and `date_trunc` still groups by the wrong day. Invariant 2 is the one that 
fixes it. Changing the default is a separate question, and would flip `now()`'s 
type a second time in five releases (aware ≤50 → naive 51+ → aware again), so 
it deserves its own discussion.
   
   Related: #13962 (user-visible symptom), #23841 (same defect in 
`to_timestamp`), #13212 (which timezone an implicit coercion should pick), 
#21116 (`TimestampWithOffset`, the direction the ecosystem is moving).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to