test_timedelta.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. """ test the scalar Timedelta """
  2. from datetime import timedelta
  3. from hypothesis import (
  4. given,
  5. strategies as st,
  6. )
  7. import numpy as np
  8. import pytest
  9. from pandas._libs import lib
  10. from pandas._libs.tslibs import (
  11. NaT,
  12. iNaT,
  13. )
  14. from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
  15. from pandas.errors import OutOfBoundsTimedelta
  16. import pandas as pd
  17. from pandas import (
  18. Timedelta,
  19. TimedeltaIndex,
  20. to_timedelta,
  21. )
  22. import pandas._testing as tm
  23. class TestAsUnit:
  24. def test_as_unit(self):
  25. td = Timedelta(days=1)
  26. assert td.as_unit("ns") is td
  27. res = td.as_unit("us")
  28. assert res._value == td._value // 1000
  29. assert res._creso == NpyDatetimeUnit.NPY_FR_us.value
  30. rt = res.as_unit("ns")
  31. assert rt._value == td._value
  32. assert rt._creso == td._creso
  33. res = td.as_unit("ms")
  34. assert res._value == td._value // 1_000_000
  35. assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
  36. rt = res.as_unit("ns")
  37. assert rt._value == td._value
  38. assert rt._creso == td._creso
  39. res = td.as_unit("s")
  40. assert res._value == td._value // 1_000_000_000
  41. assert res._creso == NpyDatetimeUnit.NPY_FR_s.value
  42. rt = res.as_unit("ns")
  43. assert rt._value == td._value
  44. assert rt._creso == td._creso
  45. def test_as_unit_overflows(self):
  46. # microsecond that would be just out of bounds for nano
  47. us = 9223372800000000
  48. td = Timedelta._from_value_and_reso(us, NpyDatetimeUnit.NPY_FR_us.value)
  49. msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow"
  50. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  51. td.as_unit("ns")
  52. res = td.as_unit("ms")
  53. assert res._value == us // 1000
  54. assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
  55. def test_as_unit_rounding(self):
  56. td = Timedelta(microseconds=1500)
  57. res = td.as_unit("ms")
  58. expected = Timedelta(milliseconds=1)
  59. assert res == expected
  60. assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
  61. assert res._value == 1
  62. with pytest.raises(ValueError, match="Cannot losslessly convert units"):
  63. td.as_unit("ms", round_ok=False)
  64. def test_as_unit_non_nano(self):
  65. # case where we are going neither to nor from nano
  66. td = Timedelta(days=1).as_unit("ms")
  67. assert td.days == 1
  68. assert td._value == 86_400_000
  69. assert td.components.days == 1
  70. assert td._d == 1
  71. assert td.total_seconds() == 86400
  72. res = td.as_unit("us")
  73. assert res._value == 86_400_000_000
  74. assert res.components.days == 1
  75. assert res.components.hours == 0
  76. assert res._d == 1
  77. assert res._h == 0
  78. assert res.total_seconds() == 86400
  79. class TestNonNano:
  80. @pytest.fixture(params=["s", "ms", "us"])
  81. def unit_str(self, request):
  82. return request.param
  83. @pytest.fixture
  84. def unit(self, unit_str):
  85. # 7, 8, 9 correspond to second, millisecond, and microsecond, respectively
  86. attr = f"NPY_FR_{unit_str}"
  87. return getattr(NpyDatetimeUnit, attr).value
  88. @pytest.fixture
  89. def val(self, unit):
  90. # microsecond that would be just out of bounds for nano
  91. us = 9223372800000000
  92. if unit == NpyDatetimeUnit.NPY_FR_us.value:
  93. value = us
  94. elif unit == NpyDatetimeUnit.NPY_FR_ms.value:
  95. value = us // 1000
  96. else:
  97. value = us // 1_000_000
  98. return value
  99. @pytest.fixture
  100. def td(self, unit, val):
  101. return Timedelta._from_value_and_reso(val, unit)
  102. def test_from_value_and_reso(self, unit, val):
  103. # Just checking that the fixture is giving us what we asked for
  104. td = Timedelta._from_value_and_reso(val, unit)
  105. assert td._value == val
  106. assert td._creso == unit
  107. assert td.days == 106752
  108. def test_unary_non_nano(self, td, unit):
  109. assert abs(td)._creso == unit
  110. assert (-td)._creso == unit
  111. assert (+td)._creso == unit
  112. def test_sub_preserves_reso(self, td, unit):
  113. res = td - td
  114. expected = Timedelta._from_value_and_reso(0, unit)
  115. assert res == expected
  116. assert res._creso == unit
  117. def test_mul_preserves_reso(self, td, unit):
  118. # The td fixture should always be far from the implementation
  119. # bound, so doubling does not risk overflow.
  120. res = td * 2
  121. assert res._value == td._value * 2
  122. assert res._creso == unit
  123. def test_cmp_cross_reso(self, td):
  124. # numpy gets this wrong because of silent overflow
  125. other = Timedelta(days=106751, unit="ns")
  126. assert other < td
  127. assert td > other
  128. assert not other == td
  129. assert td != other
  130. def test_to_pytimedelta(self, td):
  131. res = td.to_pytimedelta()
  132. expected = timedelta(days=106752)
  133. assert type(res) is timedelta
  134. assert res == expected
  135. def test_to_timedelta64(self, td, unit):
  136. for res in [td.to_timedelta64(), td.to_numpy(), td.asm8]:
  137. assert isinstance(res, np.timedelta64)
  138. assert res.view("i8") == td._value
  139. if unit == NpyDatetimeUnit.NPY_FR_s.value:
  140. assert res.dtype == "m8[s]"
  141. elif unit == NpyDatetimeUnit.NPY_FR_ms.value:
  142. assert res.dtype == "m8[ms]"
  143. elif unit == NpyDatetimeUnit.NPY_FR_us.value:
  144. assert res.dtype == "m8[us]"
  145. def test_truediv_timedeltalike(self, td):
  146. assert td / td == 1
  147. assert (2.5 * td) / td == 2.5
  148. other = Timedelta(td._value)
  149. msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow."
  150. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  151. td / other
  152. # Timedelta(other.to_pytimedelta()) has microsecond resolution,
  153. # so the division doesn't require casting all the way to nanos,
  154. # so succeeds
  155. res = other.to_pytimedelta() / td
  156. expected = other.to_pytimedelta() / td.to_pytimedelta()
  157. assert res == expected
  158. # if there's no overflow, we cast to the higher reso
  159. left = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_us.value)
  160. right = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_ms.value)
  161. result = left / right
  162. assert result == 0.001
  163. result = right / left
  164. assert result == 1000
  165. def test_truediv_numeric(self, td):
  166. assert td / np.nan is NaT
  167. res = td / 2
  168. assert res._value == td._value / 2
  169. assert res._creso == td._creso
  170. res = td / 2.0
  171. assert res._value == td._value / 2
  172. assert res._creso == td._creso
  173. def test_floordiv_timedeltalike(self, td):
  174. assert td // td == 1
  175. assert (2.5 * td) // td == 2
  176. other = Timedelta(td._value)
  177. msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow"
  178. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  179. td // other
  180. # Timedelta(other.to_pytimedelta()) has microsecond resolution,
  181. # so the floordiv doesn't require casting all the way to nanos,
  182. # so succeeds
  183. res = other.to_pytimedelta() // td
  184. assert res == 0
  185. # if there's no overflow, we cast to the higher reso
  186. left = Timedelta._from_value_and_reso(50050, NpyDatetimeUnit.NPY_FR_us.value)
  187. right = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_ms.value)
  188. result = left // right
  189. assert result == 1
  190. result = right // left
  191. assert result == 0
  192. def test_floordiv_numeric(self, td):
  193. assert td // np.nan is NaT
  194. res = td // 2
  195. assert res._value == td._value // 2
  196. assert res._creso == td._creso
  197. res = td // 2.0
  198. assert res._value == td._value // 2
  199. assert res._creso == td._creso
  200. assert td // np.array(np.nan) is NaT
  201. res = td // np.array(2)
  202. assert res._value == td._value // 2
  203. assert res._creso == td._creso
  204. res = td // np.array(2.0)
  205. assert res._value == td._value // 2
  206. assert res._creso == td._creso
  207. def test_addsub_mismatched_reso(self, td):
  208. # need to cast to since td is out of bounds for ns, so
  209. # so we would raise OverflowError without casting
  210. other = Timedelta(days=1).as_unit("us")
  211. # td is out of bounds for ns
  212. result = td + other
  213. assert result._creso == other._creso
  214. assert result.days == td.days + 1
  215. result = other + td
  216. assert result._creso == other._creso
  217. assert result.days == td.days + 1
  218. result = td - other
  219. assert result._creso == other._creso
  220. assert result.days == td.days - 1
  221. result = other - td
  222. assert result._creso == other._creso
  223. assert result.days == 1 - td.days
  224. other2 = Timedelta(500)
  225. msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow"
  226. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  227. td + other2
  228. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  229. other2 + td
  230. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  231. td - other2
  232. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  233. other2 - td
  234. def test_min(self, td):
  235. assert td.min <= td
  236. assert td.min._creso == td._creso
  237. assert td.min._value == NaT._value + 1
  238. def test_max(self, td):
  239. assert td.max >= td
  240. assert td.max._creso == td._creso
  241. assert td.max._value == np.iinfo(np.int64).max
  242. def test_resolution(self, td):
  243. expected = Timedelta._from_value_and_reso(1, td._creso)
  244. result = td.resolution
  245. assert result == expected
  246. assert result._creso == expected._creso
  247. def test_timedelta_class_min_max_resolution():
  248. # when accessed on the class (as opposed to an instance), we default
  249. # to nanoseconds
  250. assert Timedelta.min == Timedelta(NaT._value + 1)
  251. assert Timedelta.min._creso == NpyDatetimeUnit.NPY_FR_ns.value
  252. assert Timedelta.max == Timedelta(np.iinfo(np.int64).max)
  253. assert Timedelta.max._creso == NpyDatetimeUnit.NPY_FR_ns.value
  254. assert Timedelta.resolution == Timedelta(1)
  255. assert Timedelta.resolution._creso == NpyDatetimeUnit.NPY_FR_ns.value
  256. class TestTimedeltaUnaryOps:
  257. def test_invert(self):
  258. td = Timedelta(10, unit="d")
  259. msg = "bad operand type for unary ~"
  260. with pytest.raises(TypeError, match=msg):
  261. ~td
  262. # check this matches pytimedelta and timedelta64
  263. with pytest.raises(TypeError, match=msg):
  264. ~(td.to_pytimedelta())
  265. umsg = "ufunc 'invert' not supported for the input types"
  266. with pytest.raises(TypeError, match=umsg):
  267. ~(td.to_timedelta64())
  268. def test_unary_ops(self):
  269. td = Timedelta(10, unit="d")
  270. # __neg__, __pos__
  271. assert -td == Timedelta(-10, unit="d")
  272. assert -td == Timedelta("-10d")
  273. assert +td == Timedelta(10, unit="d")
  274. # __abs__, __abs__(__neg__)
  275. assert abs(td) == td
  276. assert abs(-td) == td
  277. assert abs(-td) == Timedelta("10d")
  278. class TestTimedeltas:
  279. @pytest.mark.parametrize(
  280. "unit, value, expected",
  281. [
  282. ("us", 9.999, 9999),
  283. ("ms", 9.999999, 9999999),
  284. ("s", 9.999999999, 9999999999),
  285. ],
  286. )
  287. def test_rounding_on_int_unit_construction(self, unit, value, expected):
  288. # GH 12690
  289. result = Timedelta(value, unit=unit)
  290. assert result._value == expected
  291. result = Timedelta(str(value) + unit)
  292. assert result._value == expected
  293. def test_total_seconds_scalar(self):
  294. # see gh-10939
  295. rng = Timedelta("1 days, 10:11:12.100123456")
  296. expt = 1 * 86400 + 10 * 3600 + 11 * 60 + 12 + 100123456.0 / 1e9
  297. tm.assert_almost_equal(rng.total_seconds(), expt)
  298. rng = Timedelta(np.nan)
  299. assert np.isnan(rng.total_seconds())
  300. def test_conversion(self):
  301. for td in [Timedelta(10, unit="d"), Timedelta("1 days, 10:11:12.012345")]:
  302. pydt = td.to_pytimedelta()
  303. assert td == Timedelta(pydt)
  304. assert td == pydt
  305. assert isinstance(pydt, timedelta) and not isinstance(pydt, Timedelta)
  306. assert td == np.timedelta64(td._value, "ns")
  307. td64 = td.to_timedelta64()
  308. assert td64 == np.timedelta64(td._value, "ns")
  309. assert td == td64
  310. assert isinstance(td64, np.timedelta64)
  311. # this is NOT equal and cannot be roundtripped (because of the nanos)
  312. td = Timedelta("1 days, 10:11:12.012345678")
  313. assert td != td.to_pytimedelta()
  314. def test_fields(self):
  315. def check(value):
  316. # that we are int
  317. assert isinstance(value, int)
  318. # compat to datetime.timedelta
  319. rng = to_timedelta("1 days, 10:11:12")
  320. assert rng.days == 1
  321. assert rng.seconds == 10 * 3600 + 11 * 60 + 12
  322. assert rng.microseconds == 0
  323. assert rng.nanoseconds == 0
  324. msg = "'Timedelta' object has no attribute '{}'"
  325. with pytest.raises(AttributeError, match=msg.format("hours")):
  326. rng.hours
  327. with pytest.raises(AttributeError, match=msg.format("minutes")):
  328. rng.minutes
  329. with pytest.raises(AttributeError, match=msg.format("milliseconds")):
  330. rng.milliseconds
  331. # GH 10050
  332. check(rng.days)
  333. check(rng.seconds)
  334. check(rng.microseconds)
  335. check(rng.nanoseconds)
  336. td = Timedelta("-1 days, 10:11:12")
  337. assert abs(td) == Timedelta("13:48:48")
  338. assert str(td) == "-1 days +10:11:12"
  339. assert -td == Timedelta("0 days 13:48:48")
  340. assert -Timedelta("-1 days, 10:11:12")._value == 49728000000000
  341. assert Timedelta("-1 days, 10:11:12")._value == -49728000000000
  342. rng = to_timedelta("-1 days, 10:11:12.100123456")
  343. assert rng.days == -1
  344. assert rng.seconds == 10 * 3600 + 11 * 60 + 12
  345. assert rng.microseconds == 100 * 1000 + 123
  346. assert rng.nanoseconds == 456
  347. msg = "'Timedelta' object has no attribute '{}'"
  348. with pytest.raises(AttributeError, match=msg.format("hours")):
  349. rng.hours
  350. with pytest.raises(AttributeError, match=msg.format("minutes")):
  351. rng.minutes
  352. with pytest.raises(AttributeError, match=msg.format("milliseconds")):
  353. rng.milliseconds
  354. # components
  355. tup = to_timedelta(-1, "us").components
  356. assert tup.days == -1
  357. assert tup.hours == 23
  358. assert tup.minutes == 59
  359. assert tup.seconds == 59
  360. assert tup.milliseconds == 999
  361. assert tup.microseconds == 999
  362. assert tup.nanoseconds == 0
  363. # GH 10050
  364. check(tup.days)
  365. check(tup.hours)
  366. check(tup.minutes)
  367. check(tup.seconds)
  368. check(tup.milliseconds)
  369. check(tup.microseconds)
  370. check(tup.nanoseconds)
  371. tup = Timedelta("-1 days 1 us").components
  372. assert tup.days == -2
  373. assert tup.hours == 23
  374. assert tup.minutes == 59
  375. assert tup.seconds == 59
  376. assert tup.milliseconds == 999
  377. assert tup.microseconds == 999
  378. assert tup.nanoseconds == 0
  379. def test_iso_conversion(self):
  380. # GH #21877
  381. expected = Timedelta(1, unit="s")
  382. assert to_timedelta("P0DT0H0M1S") == expected
  383. def test_nat_converters(self):
  384. result = to_timedelta("nat").to_numpy()
  385. assert result.dtype.kind == "M"
  386. assert result.astype("int64") == iNaT
  387. result = to_timedelta("nan").to_numpy()
  388. assert result.dtype.kind == "M"
  389. assert result.astype("int64") == iNaT
  390. @pytest.mark.parametrize(
  391. "unit, np_unit",
  392. [(value, "W") for value in ["W", "w"]]
  393. + [(value, "D") for value in ["D", "d", "days", "day", "Days", "Day"]]
  394. + [
  395. (value, "m")
  396. for value in [
  397. "m",
  398. "minute",
  399. "min",
  400. "minutes",
  401. "t",
  402. "Minute",
  403. "Min",
  404. "Minutes",
  405. "T",
  406. ]
  407. ]
  408. + [
  409. (value, "s")
  410. for value in [
  411. "s",
  412. "seconds",
  413. "sec",
  414. "second",
  415. "S",
  416. "Seconds",
  417. "Sec",
  418. "Second",
  419. ]
  420. ]
  421. + [
  422. (value, "ms")
  423. for value in [
  424. "ms",
  425. "milliseconds",
  426. "millisecond",
  427. "milli",
  428. "millis",
  429. "l",
  430. "MS",
  431. "Milliseconds",
  432. "Millisecond",
  433. "Milli",
  434. "Millis",
  435. "L",
  436. ]
  437. ]
  438. + [
  439. (value, "us")
  440. for value in [
  441. "us",
  442. "microseconds",
  443. "microsecond",
  444. "micro",
  445. "micros",
  446. "u",
  447. "US",
  448. "Microseconds",
  449. "Microsecond",
  450. "Micro",
  451. "Micros",
  452. "U",
  453. ]
  454. ]
  455. + [
  456. (value, "ns")
  457. for value in [
  458. "ns",
  459. "nanoseconds",
  460. "nanosecond",
  461. "nano",
  462. "nanos",
  463. "n",
  464. "NS",
  465. "Nanoseconds",
  466. "Nanosecond",
  467. "Nano",
  468. "Nanos",
  469. "N",
  470. ]
  471. ],
  472. )
  473. @pytest.mark.parametrize("wrapper", [np.array, list, pd.Index])
  474. def test_unit_parser(self, unit, np_unit, wrapper):
  475. # validate all units, GH 6855, GH 21762
  476. # array-likes
  477. expected = TimedeltaIndex(
  478. [np.timedelta64(i, np_unit) for i in np.arange(5).tolist()],
  479. dtype="m8[ns]",
  480. )
  481. # TODO(2.0): the desired output dtype may have non-nano resolution
  482. result = to_timedelta(wrapper(range(5)), unit=unit)
  483. tm.assert_index_equal(result, expected)
  484. result = TimedeltaIndex(wrapper(range(5)), unit=unit)
  485. tm.assert_index_equal(result, expected)
  486. str_repr = [f"{x}{unit}" for x in np.arange(5)]
  487. result = to_timedelta(wrapper(str_repr))
  488. tm.assert_index_equal(result, expected)
  489. result = to_timedelta(wrapper(str_repr))
  490. tm.assert_index_equal(result, expected)
  491. # scalar
  492. expected = Timedelta(np.timedelta64(2, np_unit).astype("timedelta64[ns]"))
  493. result = to_timedelta(2, unit=unit)
  494. assert result == expected
  495. result = Timedelta(2, unit=unit)
  496. assert result == expected
  497. result = to_timedelta(f"2{unit}")
  498. assert result == expected
  499. result = Timedelta(f"2{unit}")
  500. assert result == expected
  501. @pytest.mark.parametrize("unit", ["Y", "y", "M"])
  502. def test_unit_m_y_raises(self, unit):
  503. msg = "Units 'M', 'Y', and 'y' are no longer supported"
  504. with pytest.raises(ValueError, match=msg):
  505. Timedelta(10, unit)
  506. with pytest.raises(ValueError, match=msg):
  507. to_timedelta(10, unit)
  508. with pytest.raises(ValueError, match=msg):
  509. to_timedelta([1, 2], unit)
  510. def test_numeric_conversions(self):
  511. assert Timedelta(0) == np.timedelta64(0, "ns")
  512. assert Timedelta(10) == np.timedelta64(10, "ns")
  513. assert Timedelta(10, unit="ns") == np.timedelta64(10, "ns")
  514. assert Timedelta(10, unit="us") == np.timedelta64(10, "us")
  515. assert Timedelta(10, unit="ms") == np.timedelta64(10, "ms")
  516. assert Timedelta(10, unit="s") == np.timedelta64(10, "s")
  517. assert Timedelta(10, unit="d") == np.timedelta64(10, "D")
  518. def test_timedelta_conversions(self):
  519. assert Timedelta(timedelta(seconds=1)) == np.timedelta64(1, "s").astype(
  520. "m8[ns]"
  521. )
  522. assert Timedelta(timedelta(microseconds=1)) == np.timedelta64(1, "us").astype(
  523. "m8[ns]"
  524. )
  525. assert Timedelta(timedelta(days=1)) == np.timedelta64(1, "D").astype("m8[ns]")
  526. def test_to_numpy_alias(self):
  527. # GH 24653: alias .to_numpy() for scalars
  528. td = Timedelta("10m7s")
  529. assert td.to_timedelta64() == td.to_numpy()
  530. # GH#44460
  531. msg = "dtype and copy arguments are ignored"
  532. with pytest.raises(ValueError, match=msg):
  533. td.to_numpy("m8[s]")
  534. with pytest.raises(ValueError, match=msg):
  535. td.to_numpy(copy=True)
  536. @pytest.mark.parametrize(
  537. "freq,s1,s2",
  538. [
  539. # This first case has s1, s2 being the same as t1,t2 below
  540. (
  541. "N",
  542. Timedelta("1 days 02:34:56.789123456"),
  543. Timedelta("-1 days 02:34:56.789123456"),
  544. ),
  545. (
  546. "U",
  547. Timedelta("1 days 02:34:56.789123000"),
  548. Timedelta("-1 days 02:34:56.789123000"),
  549. ),
  550. (
  551. "L",
  552. Timedelta("1 days 02:34:56.789000000"),
  553. Timedelta("-1 days 02:34:56.789000000"),
  554. ),
  555. ("S", Timedelta("1 days 02:34:57"), Timedelta("-1 days 02:34:57")),
  556. ("2S", Timedelta("1 days 02:34:56"), Timedelta("-1 days 02:34:56")),
  557. ("5S", Timedelta("1 days 02:34:55"), Timedelta("-1 days 02:34:55")),
  558. ("T", Timedelta("1 days 02:35:00"), Timedelta("-1 days 02:35:00")),
  559. ("12T", Timedelta("1 days 02:36:00"), Timedelta("-1 days 02:36:00")),
  560. ("H", Timedelta("1 days 03:00:00"), Timedelta("-1 days 03:00:00")),
  561. ("d", Timedelta("1 days"), Timedelta("-1 days")),
  562. ],
  563. )
  564. def test_round(self, freq, s1, s2):
  565. t1 = Timedelta("1 days 02:34:56.789123456")
  566. t2 = Timedelta("-1 days 02:34:56.789123456")
  567. r1 = t1.round(freq)
  568. assert r1 == s1
  569. r2 = t2.round(freq)
  570. assert r2 == s2
  571. def test_round_invalid(self):
  572. t1 = Timedelta("1 days 02:34:56.789123456")
  573. for freq, msg in [
  574. ("Y", "<YearEnd: month=12> is a non-fixed frequency"),
  575. ("M", "<MonthEnd> is a non-fixed frequency"),
  576. ("foobar", "Invalid frequency: foobar"),
  577. ]:
  578. with pytest.raises(ValueError, match=msg):
  579. t1.round(freq)
  580. def test_round_implementation_bounds(self):
  581. # See also: analogous test for Timestamp
  582. # GH#38964
  583. result = Timedelta.min.ceil("s")
  584. expected = Timedelta.min + Timedelta(seconds=1) - Timedelta(145224193)
  585. assert result == expected
  586. result = Timedelta.max.floor("s")
  587. expected = Timedelta.max - Timedelta(854775807)
  588. assert result == expected
  589. with pytest.raises(OverflowError, match="value too large"):
  590. Timedelta.min.floor("s")
  591. # the second message here shows up in windows builds
  592. msg = "|".join(
  593. ["Python int too large to convert to C long", "int too big to convert"]
  594. )
  595. with pytest.raises(OverflowError, match=msg):
  596. Timedelta.max.ceil("s")
  597. @pytest.mark.xfail(reason="Failing on builds", strict=False)
  598. @given(val=st.integers(min_value=iNaT + 1, max_value=lib.i8max))
  599. @pytest.mark.parametrize(
  600. "method", [Timedelta.round, Timedelta.floor, Timedelta.ceil]
  601. )
  602. def test_round_sanity(self, val, method):
  603. val = np.int64(val)
  604. td = Timedelta(val)
  605. assert method(td, "ns") == td
  606. res = method(td, "us")
  607. nanos = 1000
  608. assert np.abs((res - td).value) < nanos
  609. assert res.value % nanos == 0
  610. res = method(td, "ms")
  611. nanos = 1_000_000
  612. assert np.abs((res - td).value) < nanos
  613. assert res.value % nanos == 0
  614. res = method(td, "s")
  615. nanos = 1_000_000_000
  616. assert np.abs((res - td).value) < nanos
  617. assert res.value % nanos == 0
  618. res = method(td, "min")
  619. nanos = 60 * 1_000_000_000
  620. assert np.abs((res - td).value) < nanos
  621. assert res.value % nanos == 0
  622. res = method(td, "h")
  623. nanos = 60 * 60 * 1_000_000_000
  624. assert np.abs((res - td).value) < nanos
  625. assert res.value % nanos == 0
  626. res = method(td, "D")
  627. nanos = 24 * 60 * 60 * 1_000_000_000
  628. assert np.abs((res - td).value) < nanos
  629. assert res.value % nanos == 0
  630. @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"])
  631. def test_round_non_nano(self, unit):
  632. td = Timedelta("1 days 02:34:57").as_unit(unit)
  633. res = td.round("min")
  634. assert res == Timedelta("1 days 02:35:00")
  635. assert res._creso == td._creso
  636. res = td.floor("min")
  637. assert res == Timedelta("1 days 02:34:00")
  638. assert res._creso == td._creso
  639. res = td.ceil("min")
  640. assert res == Timedelta("1 days 02:35:00")
  641. assert res._creso == td._creso
  642. def test_identity(self):
  643. td = Timedelta(10, unit="d")
  644. assert isinstance(td, Timedelta)
  645. assert isinstance(td, timedelta)
  646. def test_short_format_converters(self):
  647. def conv(v):
  648. return v.astype("m8[ns]")
  649. assert Timedelta("10") == np.timedelta64(10, "ns")
  650. assert Timedelta("10ns") == np.timedelta64(10, "ns")
  651. assert Timedelta("100") == np.timedelta64(100, "ns")
  652. assert Timedelta("100ns") == np.timedelta64(100, "ns")
  653. assert Timedelta("1000") == np.timedelta64(1000, "ns")
  654. assert Timedelta("1000ns") == np.timedelta64(1000, "ns")
  655. assert Timedelta("1000NS") == np.timedelta64(1000, "ns")
  656. assert Timedelta("10us") == np.timedelta64(10000, "ns")
  657. assert Timedelta("100us") == np.timedelta64(100000, "ns")
  658. assert Timedelta("1000us") == np.timedelta64(1000000, "ns")
  659. assert Timedelta("1000Us") == np.timedelta64(1000000, "ns")
  660. assert Timedelta("1000uS") == np.timedelta64(1000000, "ns")
  661. assert Timedelta("1ms") == np.timedelta64(1000000, "ns")
  662. assert Timedelta("10ms") == np.timedelta64(10000000, "ns")
  663. assert Timedelta("100ms") == np.timedelta64(100000000, "ns")
  664. assert Timedelta("1000ms") == np.timedelta64(1000000000, "ns")
  665. assert Timedelta("-1s") == -np.timedelta64(1000000000, "ns")
  666. assert Timedelta("1s") == np.timedelta64(1000000000, "ns")
  667. assert Timedelta("10s") == np.timedelta64(10000000000, "ns")
  668. assert Timedelta("100s") == np.timedelta64(100000000000, "ns")
  669. assert Timedelta("1000s") == np.timedelta64(1000000000000, "ns")
  670. assert Timedelta("1d") == conv(np.timedelta64(1, "D"))
  671. assert Timedelta("-1d") == -conv(np.timedelta64(1, "D"))
  672. assert Timedelta("1D") == conv(np.timedelta64(1, "D"))
  673. assert Timedelta("10D") == conv(np.timedelta64(10, "D"))
  674. assert Timedelta("100D") == conv(np.timedelta64(100, "D"))
  675. assert Timedelta("1000D") == conv(np.timedelta64(1000, "D"))
  676. assert Timedelta("10000D") == conv(np.timedelta64(10000, "D"))
  677. # space
  678. assert Timedelta(" 10000D ") == conv(np.timedelta64(10000, "D"))
  679. assert Timedelta(" - 10000D ") == -conv(np.timedelta64(10000, "D"))
  680. # invalid
  681. msg = "invalid unit abbreviation"
  682. with pytest.raises(ValueError, match=msg):
  683. Timedelta("1foo")
  684. msg = "unit abbreviation w/o a number"
  685. with pytest.raises(ValueError, match=msg):
  686. Timedelta("foo")
  687. def test_full_format_converters(self):
  688. def conv(v):
  689. return v.astype("m8[ns]")
  690. d1 = np.timedelta64(1, "D")
  691. assert Timedelta("1days") == conv(d1)
  692. assert Timedelta("1days,") == conv(d1)
  693. assert Timedelta("- 1days,") == -conv(d1)
  694. assert Timedelta("00:00:01") == conv(np.timedelta64(1, "s"))
  695. assert Timedelta("06:00:01") == conv(np.timedelta64(6 * 3600 + 1, "s"))
  696. assert Timedelta("06:00:01.0") == conv(np.timedelta64(6 * 3600 + 1, "s"))
  697. assert Timedelta("06:00:01.01") == conv(
  698. np.timedelta64(1000 * (6 * 3600 + 1) + 10, "ms")
  699. )
  700. assert Timedelta("- 1days, 00:00:01") == conv(-d1 + np.timedelta64(1, "s"))
  701. assert Timedelta("1days, 06:00:01") == conv(
  702. d1 + np.timedelta64(6 * 3600 + 1, "s")
  703. )
  704. assert Timedelta("1days, 06:00:01.01") == conv(
  705. d1 + np.timedelta64(1000 * (6 * 3600 + 1) + 10, "ms")
  706. )
  707. # invalid
  708. msg = "have leftover units"
  709. with pytest.raises(ValueError, match=msg):
  710. Timedelta("- 1days, 00")
  711. def test_pickle(self):
  712. v = Timedelta("1 days 10:11:12.0123456")
  713. v_p = tm.round_trip_pickle(v)
  714. assert v == v_p
  715. def test_timedelta_hash_equality(self):
  716. # GH 11129
  717. v = Timedelta(1, "D")
  718. td = timedelta(days=1)
  719. assert hash(v) == hash(td)
  720. d = {td: 2}
  721. assert d[v] == 2
  722. tds = [Timedelta(seconds=1) + Timedelta(days=n) for n in range(20)]
  723. assert all(hash(td) == hash(td.to_pytimedelta()) for td in tds)
  724. # python timedeltas drop ns resolution
  725. ns_td = Timedelta(1, "ns")
  726. assert hash(ns_td) != hash(ns_td.to_pytimedelta())
  727. def test_implementation_limits(self):
  728. min_td = Timedelta(Timedelta.min)
  729. max_td = Timedelta(Timedelta.max)
  730. # GH 12727
  731. # timedelta limits correspond to int64 boundaries
  732. assert min_td._value == iNaT + 1
  733. assert max_td._value == lib.i8max
  734. # Beyond lower limit, a NAT before the Overflow
  735. assert (min_td - Timedelta(1, "ns")) is NaT
  736. msg = "int too (large|big) to convert"
  737. with pytest.raises(OverflowError, match=msg):
  738. min_td - Timedelta(2, "ns")
  739. with pytest.raises(OverflowError, match=msg):
  740. max_td + Timedelta(1, "ns")
  741. # Same tests using the internal nanosecond values
  742. td = Timedelta(min_td._value - 1, "ns")
  743. assert td is NaT
  744. msg = "Cannot cast -9223372036854775809 from ns to 'ns' without overflow"
  745. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  746. Timedelta(min_td._value - 2, "ns")
  747. msg = "Cannot cast 9223372036854775808 from ns to 'ns' without overflow"
  748. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  749. Timedelta(max_td._value + 1, "ns")
  750. def test_total_seconds_precision(self):
  751. # GH 19458
  752. assert Timedelta("30S").total_seconds() == 30.0
  753. assert Timedelta("0").total_seconds() == 0.0
  754. assert Timedelta("-2S").total_seconds() == -2.0
  755. assert Timedelta("5.324S").total_seconds() == 5.324
  756. assert (Timedelta("30S").total_seconds() - 30.0) < 1e-20
  757. assert (30.0 - Timedelta("30S").total_seconds()) < 1e-20
  758. def test_resolution_string(self):
  759. assert Timedelta(days=1).resolution_string == "D"
  760. assert Timedelta(days=1, hours=6).resolution_string == "H"
  761. assert Timedelta(days=1, minutes=6).resolution_string == "T"
  762. assert Timedelta(days=1, seconds=6).resolution_string == "S"
  763. assert Timedelta(days=1, milliseconds=6).resolution_string == "L"
  764. assert Timedelta(days=1, microseconds=6).resolution_string == "U"
  765. assert Timedelta(days=1, nanoseconds=6).resolution_string == "N"
  766. def test_resolution_deprecated(self):
  767. # GH#21344
  768. td = Timedelta(days=4, hours=3)
  769. result = td.resolution
  770. assert result == Timedelta(nanoseconds=1)
  771. # Check that the attribute is available on the class, mirroring
  772. # the stdlib timedelta behavior
  773. result = Timedelta.resolution
  774. assert result == Timedelta(nanoseconds=1)
  775. @pytest.mark.parametrize(
  776. "value, expected",
  777. [
  778. (Timedelta("10S"), True),
  779. (Timedelta("-10S"), True),
  780. (Timedelta(10, unit="ns"), True),
  781. (Timedelta(0, unit="ns"), False),
  782. (Timedelta(-10, unit="ns"), True),
  783. (Timedelta(None), True),
  784. (NaT, True),
  785. ],
  786. )
  787. def test_truthiness(value, expected):
  788. # https://github.com/pandas-dev/pandas/issues/21484
  789. assert bool(value) is expected
  790. def test_timedelta_attribute_precision():
  791. # GH 31354
  792. td = Timedelta(1552211999999999872, unit="ns")
  793. result = td.days * 86400
  794. result += td.seconds
  795. result *= 1000000
  796. result += td.microseconds
  797. result *= 1000
  798. result += td.nanoseconds
  799. expected = td._value
  800. assert result == expected