Skip to content

Parse timestamps with Durations.ZonedTimestamp - #43

Draft
quinnj-bot[bot] wants to merge 2 commits into
mainfrom
claude/iso2timestamp
Draft

quinnj-bot[bot] wants to merge 2 commits into
mainfrom
claude/iso2timestamp

Conversation

@quinnj-bot

@quinnj-bot quinnj-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Why

Postgres.jl should not keep its own timestamp parser. Dates and Durations already parse timestamps, and JuliaData/Durations.jl#10 makes them compile under juliac --trim=safe and read PostgreSQL's UTC offsets. This PR moves the driver onto them and deletes its parser.

It stays a draft until Durations releases that change. [compat] asks for Durations 1.5, so CI cannot resolve packages before then.

What changes

  • One path for timestamp and timestamptz text. Durations.ZonedTimestamp{P,:UTC}(text, PG_TIMESTAMP_FORMAT) reads the local time with a DateFormat for PostgreSQL's ISO DateStyle, then the UTC offset when the text has one. Text without an offset reads as UTC, as before. That covers Timestamp{P} fields, the default Timestamp{Microsecond}, arrays, ranges, and Date and Time fields read from timestamp text.
  • JSON text still reads. Text with a T between the date and the time, as JSON and row_to_json write it, reads with Durations.ISOTimestampFormat, including a Z offset. The old parser accepted it too, and LeagueEasy's JSON lift relies on that.
  • DateTime fields read through Timestamp{Microsecond} and floor to milliseconds, as before. Timestamp{Microsecond}'s range ends in year 294247, before PostgreSQL's. Past it, text with no digits finer than a millisecond reads through Timestamp{Millisecond}, so PostgreSQL's last years still decode.
  • Deleted. tzoffset_seconds, parse_timestamptz, pg_parse_datetime_any, and the digit scanning in pg_parse_datetime and pg_parse_timestamp. The date and time parsers stay. Dates' DateFormat parser does not compile under trim on current Julia releases, Dates: make DateFormat parse errors compile under --trim=safe JuliaLang/julia#64621 fixes that, and Durations' fix covers only Timestamp.
  • Errors keep their types.
    • infinity, -infinity, and BC values raise PostgresInterfaceError, checked before parsing.
    • A value finer than the field's resolution raises InexactError. A value outside its range raises PostgresInterfaceError with the same advice as before.
    • Durations reports both of those as ArgumentError. So on that path the driver reads the text again at microseconds, then at milliseconds, to tell them apart. Text that no resolution reads keeps Durations' ArgumentError, as malformed text did before.

Behavior changes

These affect only text that PostgreSQL does not write in the ISO DateStyle the driver sets at connect, plus one corner of the range:

  • An offset hour past 23, an offset minute or second past 59, text after the time, and a separator other than a space or T are rejected. The old parser accepted them.
  • Text that stops before the seconds, such as 2024-02-13 05:28, reads with zero seconds, as Dates reads it.
  • A value past year 294247 that also has digits finer than a millisecond raises Durations' ArgumentError rather than PostgresInterfaceError, and a DateTime field cannot read it.
  • On Julia 1.12 and 1.13, a year of 19 or more digits wraps in Dates' digit reader before the range check. PostgreSQL writes at most six.

Verification

  • Full suite. Pkg.test() ran with Durations from Make Timestamp parsing trim-safe and read RFC 3339 text in ZonedTimestamp JuliaData/Durations.jl#10, versioned 1.5.0, and POSTGRES_REQUIRE_INTEGRATION=true against postgres:16.

    • Julia 1.13.3: 9,307 tests pass. That includes the trim build and its executable's run, which decodes DateTime and Date fields from timestamptz text in the Asia/Kolkata time zone.
    • Julia 1.10.11: 9,278 tests pass.
    • Julia 1.14.0-alpha2, which uses Dates.Timestamp: 9,304 tests pass. Trim tests skip on prereleases.
  • Trim. On Julia 1.13.3 the verifier reports 0 counted errors, the 30 known stdlib gaps that main also has, and 0 warnings.

  • Tests. New and updated tests cover:

    • DateTime and Date fields read from timestamptz text, LMT-era offset seconds, arrays, and the millisecond floor;
    • a DateTime field past Timestamp{Microsecond}'s range;
    • range errors with and without sub-millisecond digits, and UTC overflow from an offset;
    • JSON text with a T and a Z or numeric offset;
    • text the ISO DateStyle never writes.
  • Speed. Measured with @belapsed on Julia 1.13.3, per value. Two runs agreed within 4 ns on this branch and within about 10% on main.

    Text Timestamp, main → branch DateTime, main → branch
    2024-02-13 05:28:17.123456 40–43 → 87–91 ns 73–77 → 95–99 ns
    2024-02-13 05:28:17.123456+05:30 139–163 → 97 ns 206–211 → 104–105 ns
    2024-02-13 05:28:17 32–36 → 77–78 ns 47–48 → 83–86 ns

    Text with an offset, which timestamptz columns send, is faster. main built strings to read the offset and allocated 224 bytes per value, and this branch allocates none. Text without an offset, which timestamp columns send, takes about twice as long. Dates' generic DateFormat parse alone takes about 51 ns. Finding the offset and reading local time into a UTC ZonedTimestamp add about 25 ns more.

Before merging

Register a Durations release with JuliaData/Durations.jl#10, then rerun CI.

🤖 Generated with Claude Code

Durations.iso2timestamp reads ISO 8601 text with a UTC offset, checks
every field, and compiles under juliac --trim=safe, so the driver no
longer needs its own timestamp parser. Timestamp{P} fields call it
directly. DateTime fields read Timestamp{Millisecond} with RoundDown,
which handles both timestamp and timestamptz text and replaces the
offset sniffing in pg_parse_datetime_any and parse_timestamptz.

Errors keep their types. iso2timestamp reports precision and range
failures as ArgumentError, so on that path the driver parses again
with RoundDown to raise InexactError or PostgresInterfaceError as
before.

Requires Durations 1.5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Durations dropped `iso2timestamp` in favor of making its existing parsers
compile under `juliac --trim=safe`. Read `timestamp` and `timestamptz` text
with `ZonedTimestamp{P,:UTC}` and a `DateFormat` for PostgreSQL's ISO
DateStyle. It reads the UTC offset when the text has one. Text with a `T`
between the date and the time, as JSON and `row_to_json` write it, reads
with `Durations.ISOTimestampFormat`, as the old parser accepted it too.

`DateTime` fields read through `Timestamp{Microsecond}`, and past its range
through `Timestamp{Millisecond}`, so PostgreSQL's last years still decode.
On a parse error, reading again at microseconds and then milliseconds tells
digits finer than the field's resolution (`InexactError`) from a time
outside its range (`PostgresInterfaceError`). Other text keeps the parser's
`ArgumentError`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@quinnj-bot quinnj-bot Bot changed the title Parse timestamps with Durations.iso2timestamp Parse timestamps with Durations.ZonedTimestamp Nov 3, 2026

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant