hanke580 opened a new issue, #11698:
URL: https://github.com/apache/arrow-rs/issues/11698

   ### Describe the bug
   
   
   `arrow_csv::Writer` and `arrow_json::Writer` format timestamps with a time 
zone through `arrow_cast::display`.
   `write_timestamp()` (`arrow-cast/src/display.rs`, around line 770) uses
   `date.to_rfc3339_opts(SecondsFormat::AutoSi, true)`. chrono computes the 
wall time with the exact offset, but RFC 3339
   has no seconds in offsets, so the offset is printed **rounded to whole 
minutes**. The two parts of the written value
   then disagree.
   
   For `1960-06-01T12:00:00Z` in `Africa/Monrovia` (offset −00:44:30 at the 
time), the writers produce
   `1960-06-01T11:15:30-00:45`, which is `12:00:30Z`: 30 seconds later than the 
stored instant. Reading the file back
   (with arrow-csv/arrow-json, DataFusion, or any other tool) gives the shifted 
instant. There is no error.
   
   This applies to every zone whose offset was not a whole number of minutes: 
Africa/Monrovia until 1972, Europe/Dublin
   until 1916, Europe/Paris until 1911, and every zone's local mean time (LMT) 
before standard time, e.g.
   `America/New_York` before 1883. Parquet and IPC are not affected.
   
   Writing the instant in UTC (`Z`), or the offset with seconds (`-00:44:30`, 
which chrono and most parsers accept even
   though it is outside RFC 3339), would be lossless.
   
   ### To Reproduce
   
   
   ```rust
   use std::sync::Arc;
   use arrow::array::{ArrayRef, TimestampMicrosecondArray};
   use arrow::datatypes::{DataType, Field, Schema, TimeUnit};
   use arrow::record_batch::RecordBatch;
   
   fn main() {
       // 1960-06-01T12:00:00Z; Africa/Monrovia's offset was then -00:44:30
       let ts = 
TimestampMicrosecondArray::from(vec![-302_443_200_000_000i64]).with_timezone("Africa/Monrovia");
       let schema = Arc::new(Schema::new(vec![Field::new("ts",
           DataType::Timestamp(TimeUnit::Microsecond, 
Some("Africa/Monrovia".into())), false)]));
       let batch = RecordBatch::try_new(schema, vec![Arc::new(ts) as 
ArrayRef]).unwrap();
   
       let mut csv = Vec::new();
       arrow::csv::Writer::new(&mut csv).write(&batch).unwrap();
       println!("csv:  {}", String::from_utf8(csv).unwrap());
   
       let mut json = arrow::json::LineDelimitedWriter::new(Vec::new());
       json.write(&batch).unwrap();
       json.finish().unwrap();
       println!("json: {}", String::from_utf8(json.into_inner()).unwrap());
   }
   ```
   
   (`arrow = { version = "60.0.0", features = ["chrono-tz", "csv", "json"] }`)
   
   ```
   csv:  ts
   1960-06-01T11:15:30-00:45
   json: {"ts":"1960-06-01T11:15:30-00:45"}
   ```
   
   `11:15:30-00:45` is `12:00:30Z`. The array holds `12:00:00Z`.
   
   The same through DataFusion (`datafusion-cli` 55.1.0, main 290ad15), 
including a read back:
   
   ```sql
   CREATE TABLE t AS SELECT arrow_cast(arrow_cast('1960-06-01T12:00:00Z', 
'Timestamp(Microsecond, Some("UTC"))'),
                                       'Timestamp(Microsecond, 
Some("Africa/Monrovia"))') AS ts;
   COPY t TO 'o/t.csv' STORED AS CSV;          -- 1960-06-01T11:15:30-00:45
   CREATE EXTERNAL TABLE c (ts TIMESTAMPTZ) STORED AS CSV LOCATION 'o/t.csv' 
OPTIONS ('format.has_header' 'true');
   SELECT ts FROM c;                           -- 1960-06-01T12:00:30
   ```
   
   | zone, stored instant | written | read back (UTC) |
   |---|---|---|
   | Africa/Monrovia, 1960-06-01T12:00:00Z | `1960-06-01T11:15:30-00:45` | 
12:00:30 |
   | Europe/Dublin, 1900-06-01T12:00:00Z | `1900-06-01T11:34:39-00:25` | 
11:59:39 |
   | Europe/Paris, 1900-06-01T12:00:00Z | `1900-06-01T12:09:21+00:09` | 
12:00:21 |
   | America/New_York, 1880-06-01T12:00:00Z | `1880-06-01T07:03:58-04:56` | 
11:59:58 |
   | Africa/Monrovia, 2000-06-01T12:00:00Z | `2000-06-01T12:00:00Z` | 12:00:00 |
   
   
   ### Expected behavior
   
   The written text denotes the same instant as the array value, e.g. 
`1960-06-01T12:00:00Z` or `1960-06-01T11:15:30-00:44:30`.
   
   
   ### Additional context
   
   - arrow 60.0.0. `write_timestamp` in `arrow-cast/src/display.rs` is 
unchanged on `main` (last change to the file
     2026-09-29).
   - In DataFusion this also affects `INSERT INTO` a CSV/JSON-backed table 
under a session time zone: with
     `SET datafusion.execution.time_zone = 'Africa/Monrovia'`, `INSERT … VALUES 
(1, TIMESTAMPTZ '1960-06-01T12:00:00Z')`
     stores `1960-06-01T11:15:30-00:45` in the file and reads back `12:00:30Z`.
   


-- 
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]

Reply via email to