You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Document the temporal type contract and gate the release on it #1221
Document the temporal contract and gate the release on it.
Scope
A documentation page with the contract table, the supported years (1 to 9999; others are out of scope), precision, NULL handling, time zones (the Define time zone conversion for values whose zone cannot be represented #1220 decisions, and the limitation that Athena's and Python's time zone rules can differ, with AT TIME ZONE 'UTC' or to_unixtime() for exact instants), and the UNLOAD limits (only TIMESTAMP(3); no time or with time zone types).
Release notes for 4.2.0 (silent wrong values fixed, the tzdata>=2024.2 floor) with migration notes (dtypes, fetched types, and DataError for time zone errors).
Benchmarks of elapsed time and peak RSS before and after, for the readers whose conversion changed.
With unload=True, PandasCursor, ArrowCursor, and PolarsCursor read timestamps at microseconds. Values outside 1677-09-21 to 2262-04-11 were wrapped or saturated before: 1600-01-02 03:04:05.123 was read as 2184-07-22 02:38:38.832551616 by PandasCursor and ArrowCursor, and as 2262-04-11 23:47:16.854775807 by PolarsCursor. This includes timestamps inside arrays, rows, and maps.
Behavior changes (migration notes)
The dtype of UNLOAD timestamps changes from nanoseconds to microseconds, as for CSV results: datetime64[us], timestamp[us], Datetime("us").
PandasCursor: coerce_int96_timestamp_unit given to execute() still replaces the unit.
PolarsCursor: a schema given to execute() is used as given.
PolarsCursor with Polars 1.x cannot read these timestamps at microseconds itself.
Without read options given to execute() and with pyarrow installed, a whole UNLOAD read of a result with timestamps goes through pyarrow and PyAthena's S3FileSystem, and logs a warning that recommends Polars 2.0. In a local measurement it was about 4x slower and used about 1.8x the memory.
Chunked reads without read options already use pyarrow.
Otherwise (no pyarrow, or other read options such as storage_options or columns), Polars 1.x reads natively as before. The timestamps are cast to microseconds, and a warning says that values outside 1677-09-21 to 2262-04-11 are wrong.
On a local file of 5M rows (INT96 timestamps and strings), median of 5 reads, the time and peak RSS of the Polars 2.0, ArrowCursor, and PandasCursor whole reads did not change beyond run-to-run variation.
The Polars 1.x pyarrow read went from 17-19 ms / 339-354 MiB to 60-78 ms / 593-671 MiB.
Draft of the 4.2.0 release notes for the temporal type contract (#1212). It consolidates the items of #1224, #1233, #1234, #1236, #1273, and #1277 (and the comment above), and takes the Polars 1.x item from #1282, which is not merged yet. The contract is documented in docs/temporal_types.md (#1284).
Temporal types
Every cursor now returns one type per Athena temporal type, whatever the values and the read mode: see Temporal Types.
Fixed: silent wrong values
With unload=True, PandasCursor, ArrowCursor, and PolarsCursor read timestamps at microseconds, including timestamps inside arrays, rows, and maps (Read UNLOAD timestamps at microseconds #1233). Values outside 1677-09-21 to 2262-04-11 were wrapped or saturated before: 1600-01-02 03:04:05.123 was read as 2184-07-22 02:38:38.832551616 by PandasCursor and ArrowCursor, and as 2262-04-11 23:47:16.854775807 by PolarsCursor. For Polars 1.x, see the behavior changes.
Chunked PolarsCursor reads of CSV results no longer fail on timestamps with more than nine fractional digits (TIMESTAMP(10) to TIMESTAMP(12)); the digits beyond the time unit are truncated (Convert the timestamp text of chunked Polars CSV reads in the scan #1234). With ignore_errors=True, whole and chunked reads truncate such timestamps instead of returning null.
Behavior changes (migration notes)
UNLOAD timestamps are microseconds instead of nanoseconds, as for CSV results: datetime64[us], timestamp[us], and Datetime("us") (Read UNLOAD timestamps at microseconds #1233).
PandasCursor: coerce_int96_timestamp_unit given to execute() still replaces the unit.
PolarsCursor: a schema given to execute() is used as given.
PolarsCursor with Polars 1.x reads UNLOAD timestamps at nanoseconds and casts them to microseconds, so values outside 1677-09-21 to 2262-04-11 are wrong, and logs a warning that recommends Polars 2.0 or later. Chunked reads with pyarrow installed and no Polars read options read them correctly (Remove the Polars 1.x pyarrow fallback for whole UNLOAD reads with timestamps (GH-1280) #1282).
timestamp is always datetime64[us], with digits beyond microseconds truncated. It was [ns] for 7 or more fractional digits, [s] when all NULL or with engine="pyarrow" and no fraction, [ns] with engine="pyarrow" and a fraction, and str for a column that also held values outside the nanosecond range.
timestamp with time zone is datetime64[us, UTC]. It had the data's zone, or was object for several zones or an all-NULL column.
date from UNLOAD results is datetime64[us], as from CSV results. It was object (date).
The fetch methods return datetime, date, and aware datetime in UTC, and None for NULL, instead of pd.Timestamp and NaT.
With parse_dates, dtype, or converters given to execute(), PyAthena leaves the temporal columns to pandas, and the fetch methods return pandas' values for them.
engine="pyarrow" is now also used for results with timestamp with time zone columns.
A timestamp with time zone value with an unknown zone name, or with a local time that its zone repeats or skips (daylight saving time), raises DataError instead of returning a naive datetime or the earlier instant (Raise DataError for time zone values without one instant #1224). Convert such values to UTC in SQL (AT TIME ZONE 'UTC') or read to_unixtime().
Closed by #1284 (merged as 7b2a084), which documents the contract. The 4.2.0 release-note draft is in #1221 (comment), and the benchmarks moved to #1287.
Activity
laughingman7743 commentedon Oct 17, 2026
Release-note items from #1233 (#1214), merged as 4ac04db:
Fixed (silent wrong values)
unload=True, PandasCursor, ArrowCursor, and PolarsCursor read timestamps at microseconds. Values outside 1677-09-21 to 2262-04-11 were wrapped or saturated before:1600-01-02 03:04:05.123was read as2184-07-22 02:38:38.832551616by PandasCursor and ArrowCursor, and as2262-04-11 23:47:16.854775807by PolarsCursor. This includes timestamps inside arrays, rows, and maps.Behavior changes (migration notes)
datetime64[us],timestamp[us],Datetime("us").coerce_int96_timestamp_unitgiven toexecute()still replaces the unit.schemagiven toexecute()is used as given.execute()and with pyarrow installed, a whole UNLOAD read of a result with timestamps goes through pyarrow and PyAthena's S3FileSystem, and logs a warning that recommends Polars 2.0. In a local measurement it was about 4x slower and used about 1.8x the memory.storage_optionsorcolumns), Polars 1.x reads natively as before. The timestamps are cast to microseconds, and a warning says that values outside 1677-09-21 to 2262-04-11 are wrong.Map.Benchmark input for this issue
laughingman7743 commentedon Oct 18, 2026
Draft of the 4.2.0 release notes for the temporal type contract (#1212). It consolidates the items of #1224, #1233, #1234, #1236, #1273, and #1277 (and the comment above), and takes the Polars 1.x item from #1282, which is not merged yet. The contract is documented in
docs/temporal_types.md(#1284).Temporal types
Every cursor now returns one type per Athena temporal type, whatever the values and the read mode: see Temporal Types.
Fixed: silent wrong values
unload=True, PandasCursor, ArrowCursor, and PolarsCursor read timestamps at microseconds, including timestamps inside arrays, rows, and maps (Read UNLOAD timestamps at microseconds #1233). Values outside 1677-09-21 to 2262-04-11 were wrapped or saturated before:1600-01-02 03:04:05.123was read as2184-07-22 02:38:38.832551616by PandasCursor and ArrowCursor, and as2262-04-11 23:47:16.854775807by PolarsCursor. For Polars 1.x, see the behavior changes.time,time with time zone, andtimestamp with time zonevalues of CSV results as null instead of''(Read CSV dates as date32 and temporal text NULL as null in ArrowCursor #1236).TIMESTAMP(10)toTIMESTAMP(12)); the digits beyond the time unit are truncated (Convert the timestamp text of chunked Polars CSV reads in the scan #1234). Withignore_errors=True, whole and chunked reads truncate such timestamps instead of returning null.Behavior changes (migration notes)
datetime64[us],timestamp[us], andDatetime("us")(Read UNLOAD timestamps at microseconds #1233).coerce_int96_timestamp_unitgiven toexecute()still replaces the unit.schemagiven toexecute()is used as given.timestampis alwaysdatetime64[us], with digits beyond microseconds truncated. It was[ns]for 7 or more fractional digits,[s]when all NULL or withengine="pyarrow"and no fraction,[ns]withengine="pyarrow"and a fraction, andstrfor a column that also held values outside the nanosecond range.timestamp with time zoneisdatetime64[us, UTC]. It had the data's zone, or wasobjectfor several zones or an all-NULL column.datefrom UNLOAD results isdatetime64[us], as from CSV results. It wasobject(date).datetime,date, and awaredatetimein UTC, andNonefor NULL, instead ofpd.TimestampandNaT.parse_dates,dtype, orconvertersgiven toexecute(), PyAthena leaves the temporal columns to pandas, and the fetch methods return pandas' values for them.engine="pyarrow"is now also used for results withtimestamp with time zonecolumns.datefrom CSV results asdate32instead oftimestamp[ms], as from UNLOAD results;as_polars()givespl.Dateinstead ofpl.Datetime("ms")(Read CSV dates as date32 and temporal text NULL as null in ArrowCursor #1236).timestamp with time zonevalue with an unknown zone name, or with a local time that its zone repeats or skips (daylight saving time), raisesDataErrorinstead of returning a naivedatetimeor the earlier instant (Raise DataError for time zone values without one instant #1224). Convert such values to UTC in SQL (AT TIME ZONE 'UTC') or readto_unixtime().DataErrornaming the column (Column 'name': ...) in every cursor (Give PandasCursor's temporal columns one dtype per Athena type #1273, Name the column in DataError from the fetch methods of every cursor #1277). PandasCursor whole reads raise it instead of anOperationalErrorthat wrapped it; aDataErrorfrom a function given inconvertersis still wrapped inOperationalError.tzdata>=2024.2is required (Raise DataError for time zone values without one instant #1224).Say where the cursors raise DataError for time zone values (GH-1221)
Say where the cursors raise DataError for time zone values (GH-1221)
Exclude PandasCursor from the time zone types of the fetch methods (G…
State the UTC year range of PandasCursor and when DataError is raised (…
Name chunked PandasCursor reads among the places DataError is raised (G…
Name chunked PandasCursor reads among the places DataError is raised (G…
Give examples instead of a list of where DataError is raised (GH-1221)
Merge pull request #1284 from pyathena-dev/docs/1221-temporal-types
laughingman7743 commentedon Oct 18, 2026
Closed by #1284 (merged as 7b2a084), which documents the contract. The 4.2.0 release-note draft is in #1221 (comment), and the benchmarks moved to #1287.