Skip to content

PolarsCursor truncates timestamp fractions wrongly for years outside 0000-9999 #1201

Closed as not planned

Description

@laughingman7743

Summary

PolarsCursor truncates the fraction of a timestamp to the wrong digits when its year is not written with four digits.

_to_datetimes() (pyathena/polars/converter.py) cuts the timestamp text at a fixed length from _TIMESTAMP_TEXT_LENGTHS (pyathena/converter.py), such as 23 characters for ms and 26 for us, which assumes a four-digit year. Athena writes a year after 9999 with a + sign and five or more digits, and a year before 0001 with a - sign, so the cut drops fractional digits that the time unit holds. The values are returned without an error.

_to_datetimes() converts the timestamp columns of a CSV result read in chunks, and of a whole read whose native parse fails.

Reproduction

Athena writes these values in the CSV result file and the GetQueryResults rows (checked with a live query):

SELECT
  date_add('year', 9000, CAST('2020-01-02 03:04:05.123' AS TIMESTAMP(3))),
  date_add('year', -2021, CAST('2020-01-02 03:04:05.123456789012' AS TIMESTAMP(12)))
-- "+11020-01-02 03:04:05.123", "-0001-01-02 03:04:05.123456789012"

_to_datetimes() returns, with Polars 1.39.0 to 2.0.0:

text Datetime("ms") Datetime("us")
+11020-01-02 03:04:05.123 +11020-01-02 03:04:05.100 +11020-01-02 03:04:05.123000
+11020-01-02 03:04:05.123456789012 +11020-01-02 03:04:05.100 +11020-01-02 03:04:05.123400
-0001-01-02 03:04:05.123456789012 -0001-01-02 03:04:05.120 -0001-01-02 03:04:05.123450

Expected behavior

The fraction is truncated to the time unit regardless of the width of the year: .123 for ms and .123456 for us.

polars.Expr.str.to_datetime() with the format "%Y-%m-%d %H:%M:%S%.f" already parses these years and truncates fractional digits beyond the time unit, on Polars 1.39.0, 1.44.2, and 2.0.0, so the text does not need to be cut.

ArrowCursor shares _TIMESTAMP_TEXT_LENGTHS, but pyarrow does not parse a signed or five-digit year at all, so its read fails with ArrowInvalid before the cut matters.

Activity

  1. laughingman7743 commented on Oct 16, 2026

    @laughingman7743
    MemberAuthor

    Closing in favor of #1216 under the temporal contract in #1212.

    The contract limits typed values to years 1 to 9999 in every cursor; a year written with a sign or five or more digits raises DataError instead of being parsed. This issue's expected behavior, truncating the fraction correctly whatever the width of the year, no longer applies. The fixed-length cut that caused it is handled in #1216, together with the year range.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions