Skip to content

Commit e42a196

Browse files
committed
BUG: Bug in rounding of negative Timedeltas
1 parent 5bc191a commit e42a196

File tree

4 files changed

+34
-46
lines changed

4 files changed

+34
-46
lines changed

doc/source/whatsnew/v0.18.0.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Backwards incompatible API changes
5757
Other API Changes
5858
^^^^^^^^^^^^^^^^^
5959

60-
60+
- ``Timedelta.resolution`` will now return proper offset frequency strings
6161

6262

6363

@@ -107,5 +107,5 @@ Bug Fixes
107107
~~~~~~~~~
108108

109109

110-
110+
- Bug in ``Timedelta.round`` with negative values (:issue:``)
111111
- Bug in ``.loc`` against ``CategoricalIndex`` may result in normal ``Index`` (:issue:`11586`)

pandas/tseries/tdi.py

+2-13
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,6 @@
2424

2525
Timedelta = tslib.Timedelta
2626

27-
_resolution_map = {
28-
'ns' : offsets.Nano,
29-
'us' : offsets.Micro,
30-
'ms' : offsets.Milli,
31-
's' : offsets.Second,
32-
'm' : offsets.Minute,
33-
'h' : offsets.Hour,
34-
'D' : offsets.Day,
35-
}
36-
3727
def _td_index_cmp(opname, nat_result=False):
3828
"""
3929
Wrap comparison operations to convert timedelta-like to timedelta64
@@ -706,7 +696,7 @@ def _maybe_cast_slice_bound(self, label, side, kind):
706696
if side == 'left':
707697
return lbound
708698
else:
709-
return (lbound + _resolution_map[parsed.resolution]() -
699+
return (lbound + to_offset(parsed.resolution) -
710700
Timedelta(1, 'ns'))
711701
elif is_integer(label) or is_float(label):
712702
self._invalid_indexer('slice',label)
@@ -734,9 +724,8 @@ def _partial_td_slice(self, key, freq, use_lhs=True, use_rhs=True):
734724

735725
# figure out the resolution of the passed td
736726
# and round to it
737-
reso = parsed.resolution
738727
t1 = parsed.round(reso)
739-
t2 = t1 + _resolution_map[reso]() - Timedelta(1,'ns')
728+
t2 = t1 + to_offset(parsed.resolution) - Timedelta(1,'ns')
740729

741730
stamps = self.asi8
742731

pandas/tseries/tests/test_timedeltas.py

+17
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,23 @@ def test_construction(self):
164164
self.assertEqual(Timedelta(pd.offsets.Hour(2)),Timedelta('0 days, 02:00:00'))
165165
self.assertEqual(Timedelta(pd.offsets.Second(2)),Timedelta('0 days, 00:00:02'))
166166

167+
def test_round(self):
168+
169+
t1 = Timedelta('1 days 02:34:56.789123456')
170+
t2 = Timedelta('-1 days 02:34:56.789123456')
171+
172+
for (reso, s1, s2) in [('N', t1, t2),
173+
('U', Timedelta('1 days 02:34:56.789123000'),Timedelta('-1 days 02:34:56.789123000')),
174+
('L', Timedelta('1 days 02:34:56.789000000'),Timedelta('-1 days 02:34:56.789000000')),
175+
('S', Timedelta('1 days 02:34:56'),Timedelta('-1 days 02:34:56')),
176+
('T', Timedelta('1 days 02:34:00'),Timedelta('-1 days 02:34:00')),
177+
('H', Timedelta('1 days 02:00:00'),Timedelta('-1 days 02:00:00')),
178+
('d', Timedelta('1 days'),Timedelta('-1 days'))]:
179+
r1 = t1.round(reso)
180+
self.assertEqual(r1, s1)
181+
r2 = t2.round(reso)
182+
self.assertEqual(r2, s2)
183+
167184
def test_repr(self):
168185

169186
self.assertEqual(repr(Timedelta(10,unit='d')),"Timedelta('10 days 00:00:00')")

pandas/tslib.pyx

+13-31
Original file line numberDiff line numberDiff line change
@@ -2301,52 +2301,34 @@ class Timedelta(_Timedelta):
23012301

23022302
self._ensure_components()
23032303
if self._ns:
2304-
return "ns"
2304+
return "N"
23052305
elif self._us:
2306-
return "us"
2306+
return "U"
23072307
elif self._ms:
2308-
return "ms"
2308+
return "L"
23092309
elif self._s:
2310-
return "s"
2310+
return "S"
23112311
elif self._m:
2312-
return "m"
2312+
return "T"
23132313
elif self._h:
2314-
return "h"
2314+
return "H"
23152315
else:
23162316
return "D"
23172317

2318-
def round(self, reso):
2318+
def round(self, freq):
23192319
"""
23202320
return a new Timedelta rounded to this resolution
23212321
23222322
Parameters
23232323
----------
2324-
reso : a string indicating the rouding resolution, accepting values
2325-
d,h,m,s,ms,us
2326-
2324+
freq : a freq string indicating the rouding resolution
23272325
"""
2328-
cdef int64_t frac, value = np.abs(self.value)
2329-
2330-
self._ensure_components()
2331-
frac = int(self._ms*1e6 + self._us*1e3+ self._ns)
2332-
if reso == 'us':
2333-
value -= self._ns
2334-
elif reso == 'ms':
2335-
value -= self._us*1000 + self._ns
2336-
elif reso == 's':
2337-
value -= frac
2338-
elif reso == 'm':
2339-
value -= int(self._s*1e9) + frac
2340-
elif reso == 'h':
2341-
value -= int((60*self._m + self._s)*1e9) + frac
2342-
elif reso == 'd' or reso == 'D':
2343-
value -= int((3600*self._h + 60*self._m + self._s)*1e9) + frac
2344-
else:
2345-
raise ValueError("invalid resolution")
2326+
cdef int64_t result, unit
23462327

2347-
if self._sign < 0:
2348-
value *= -1
2349-
return Timedelta(value,unit='ns')
2328+
from pandas.tseries.frequencies import to_offset
2329+
unit = to_offset(freq).nanos
2330+
result = unit*np.floor(self.value/unit)
2331+
return Timedelta(result,unit='ns')
23502332

23512333
def _repr_base(self, format=None):
23522334
"""

0 commit comments

Comments
 (0)