Skip to content

Fix Timestamp.from_datetime returning wrong value for pre-epoch datetimes#662

Open
bysiber wants to merge 1 commit intomsgpack:mainfrom
bysiber:fix/timestamp-from-datetime-pre-epoch
Open

Fix Timestamp.from_datetime returning wrong value for pre-epoch datetimes#662
bysiber wants to merge 1 commit intomsgpack:mainfrom
bysiber:fix/timestamp-from-datetime-pre-epoch

Conversation

@bysiber
Copy link
Copy Markdown

@bysiber bysiber commented Feb 20, 2026

Timestamp.from_datetime() uses int(dt.timestamp()) to compute the seconds component, but int() truncates towards zero. For pre-epoch datetimes with non-zero microseconds, this produces the wrong value:

import datetime
dt = datetime.datetime(1969, 12, 31, 23, 59, 59, 500000, tzinfo=datetime.timezone.utc)
# dt.timestamp() == -0.5
# int(-0.5) == 0  (truncation towards zero)
# Expected: Timestamp(seconds=-1, nanoseconds=500000000)
# Actual:   Timestamp(seconds=0, nanoseconds=500000000)  <-- +0.5s instead of -0.5s!

The sign of the time gets flipped. from_unix() already handles this correctly using floor division (int(unix_sec // 1)). This change makes from_datetime() consistent.

int() truncates towards zero, so for pre-epoch datetimes with non-zero
microseconds the seconds component gets the wrong value. For example,
datetime(1969, 12, 31, 23, 59, 59, 500000, UTC) has timestamp -0.5,
but int(-0.5) == 0, producing Timestamp(0, 500000000) (+0.5s after
epoch) instead of Timestamp(-1, 500000000) (-0.5s before epoch).

Use floor division (// 1) instead, consistent with from_unix().
@methane
Copy link
Copy Markdown
Member

methane commented Feb 24, 2026

@bysiber would you add a testcase for the fix to test_timestamp.py?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes incorrect Timestamp.from_datetime() results for pre-epoch datetimes with fractional seconds by ensuring the seconds component is computed via flooring (consistent with Timestamp.from_unix()), avoiding int()’s truncation-toward-zero behavior.

Changes:

  • Update Timestamp.from_datetime() to compute seconds using int(dt.timestamp() // 1) instead of int(dt.timestamp()).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread msgpack/ext.py
:rtype: Timestamp
"""
return Timestamp(seconds=int(dt.timestamp()), nanoseconds=dt.microsecond * 1000)
return Timestamp(seconds=int(dt.timestamp() // 1), nanoseconds=dt.microsecond * 1000)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

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.

3 participants