Skip to content

Commit ab701a8

Browse files
committed
BUG: Rounding error in Timestamp.floor() for dates far in the future and past (GH19206)
1 parent 8347ff8 commit ab701a8

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

doc/source/whatsnew/v0.23.0.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,6 @@ Documentation Changes
354354
Bug Fixes
355355
~~~~~~~~~
356356

357-
358357
Conversion
359358
^^^^^^^^^^
360359

@@ -462,6 +461,7 @@ Numeric
462461
- Bug in :class:`DatetimeIndex`, :class:`TimedeltaIndex` addition and subtraction of zero-dimensional integer arrays gave incorrect results (:issue:`19012`)
463462
- Bug in :func:`Series.__add__` adding Series with dtype ``timedelta64[ns]`` to a timezone-aware ``DatetimeIndex`` incorrectly dropped timezone information (:issue:`13905`)
464463
- Bug in :func:`Timedelta.__floordiv__` and :func:`Timedelta.__rfloordiv__` dividing by many incompatible numpy objects was incorrectly allowed (:issue:`18846`)
464+
- Bug in :func:`Timestamp.floor` where time stamps far in the future and past were not rounded correctly (:issue:`19206`)
465465
-
466466

467467
Categorical

pandas/_libs/tslibs/timestamps.pyx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -600,17 +600,30 @@ class Timestamp(_Timestamp):
600600
value = self.tz_localize(None).value
601601
else:
602602
value = self.value
603+
604+
# GH 19206
605+
# to deal with round-off when unit is large
606+
if unit >= 1e9:
607+
divisor = 10 ** int(np.log10(unit / 1e7))
608+
else:
609+
divisor = 1
610+
603611
if unit < 1000 and unit % 1000 != 0:
604612
# for nano rounding, work with the last 6 digits separately
605613
# due to float precision
606614
r = (buff * (value // buff) + unit *
607-
(rounder((value % buff) / float(unit))).astype('i8'))
615+
(rounder((value % buff) * (1 / float(unit)))).astype('i8'))
608616
elif unit >= 1000 and unit % 1000 != 0:
609617
msg = 'Precision will be lost using frequency: {}'
610618
warnings.warn(msg.format(freq))
611-
r = (unit * rounder(value / float(unit)).astype('i8'))
619+
r = (unit *
620+
rounder((value * divisor / float(unit)) / divisor)
621+
.astype('i8'))
612622
else:
613-
r = (unit * rounder(value / float(unit)).astype('i8'))
623+
r = (unit *
624+
rounder((value * divisor / float(unit)) / divisor)
625+
.astype('i8'))
626+
614627
result = Timestamp(r, unit='ns')
615628
if self.tz is not None:
616629
result = result.tz_localize(self.tz)

pandas/tests/scalar/test_timestamp.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,27 @@ def test_round(self):
695695
expected = Timestamp('20130101')
696696
assert result == expected
697697

698+
# GH 19206
699+
dt = Timestamp('2117-01-01 00:00:45')
700+
result = dt.floor('15s')
701+
expected = Timestamp('2117-01-01 00:00:45')
702+
assert result == expected
703+
704+
dt = Timestamp('2117-01-01 00:00:45.000000012')
705+
result = dt.floor('10ns')
706+
expected = Timestamp('2117-01-01 00:00:45.000000010')
707+
assert result == expected
708+
709+
dt = Timestamp('1823-01-01 00:00:01')
710+
result = dt.floor('1s')
711+
expected = Timestamp('1823-01-01 00:00:01')
712+
assert result == expected
713+
714+
dt = Timestamp('1823-01-01 00:00:01.000000012')
715+
result = dt.floor('10ns')
716+
expected = Timestamp('1823-01-01 00:00:01.000000010')
717+
assert result == expected
718+
698719
# ceil
699720
dt = Timestamp('20130101 09:10:11')
700721
result = dt.ceil('D')

0 commit comments

Comments
 (0)