Reject timestamptz values that do not fit the 64-bit wire format - #793
Merged
josevalim merged 3 commits intoSep 3, 2026
Merged
Conversation
DateTime values beyond PostgreSQL's timestamp range were passed to the binary construction unchecked, which truncates them to 64 bits. The server then receives a different, in-range timestamp and stores it instead of rejecting it. The two endpoints of the range are the infinity sentinels, so a finite DateTime could also be stored as infinity. The sibling timestamp extension already guards its input this way. Co-authored-by: Claude <noreply@anthropic.com>
josevalim
reviewed
Sep 3, 2026
Member
|
💚 💙 💜 💛 ❤️ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TimestampTZ.encode_elixir/1writes the microsecond value into anint64field without checking that it fits. Erlang truncates silently, so PostgreSQL
receives a different timestamp and stores that instead.
#689 removed the year cap on the grounds that "Postgres will return an error if
the timestamp is out of range". That holds for values that survive the cast, but
not for ones that wrap back into the accepted range.
Measured against PostgreSQL 17.9, inserting into a
timestamptzcolumn:294277-01-09 04:00:54.775807Zinfinity(isfinite= false)585706-01-011151-12-14 ...587483-01-012928-12-13 ...294277-01-01,300000,1000000The first is exact rather than lucky: it encodes to 9223372036854775807, which
is the
infinitysentinel. Sampling years 294278..2000000, 477 of 960 land backinside the accepted range.
Reachable from ordinary arithmetic, e.g.
DateTime.add(dt, n, :microsecond)with a large
n.The sibling
timestampextension already bounds its input and rejects the sameyear. #689 only added a test for the negative bound, since at the time the upper
bound "will crash on most elixir versions" — on Elixir 1.20 it no longer does.
Reverting the guard fails the new test. Guarding only the two sentinels also
fails it, because the wrap cases still pass through.
Assisted by Claude (
claude-opus-5); I reviewed and verified the change.