@@ -1908,15 +1908,24 @@ def delta_to_json(delta: timedelta) -> str:
1908
1908
class _Timestamp (Timestamp ):
1909
1909
@classmethod
1910
1910
def from_datetime (cls , dt : datetime ) -> "_Timestamp" :
1911
- # apparently 0 isn't a year in [0, 9999]??
1912
- seconds = int ((dt - DATETIME_ZERO ).total_seconds ())
1913
- nanos = int (dt .microsecond * 1e3 )
1914
- return cls (seconds , nanos )
1911
+ # manual epoch offset calulation to avoid rounding errors,
1912
+ # to support negative timestamps (before 1970) and skirt
1913
+ # around datetime bugs (apparently 0 isn't a year in [0, 9999]??)
1914
+ offset = dt - DATETIME_ZERO
1915
+ # below is the same as timedelta.total_seconds() but without dividing by 1e6
1916
+ # so we end up with microseconds as integers instead of seconds as float
1917
+ offset_us = (
1918
+ offset .days * 24 * 60 * 60 + offset .seconds
1919
+ ) * 10 ** 6 + offset .microseconds
1920
+ seconds , us = divmod (offset_us , 10 ** 6 )
1921
+ return cls (seconds , us * 1000 )
1915
1922
1916
1923
def to_datetime (self ) -> datetime :
1917
- ts = self .seconds + (self .nanos / 1e9 )
1918
- # if datetime.fromtimestamp ever supports -62135596800 use that instead see #407
1919
- return DATETIME_ZERO + timedelta (seconds = ts )
1924
+ # datetime.fromtimestamp() expects a timestamp in seconds, not microseconds
1925
+ # if we pass it as a floating point number, we will run into rounding errors
1926
+ # see also #407
1927
+ offset = timedelta (seconds = self .seconds , microseconds = self .nanos // 1000 )
1928
+ return DATETIME_ZERO + offset
1920
1929
1921
1930
@staticmethod
1922
1931
def timestamp_to_json (dt : datetime ) -> str :
0 commit comments