test_constructors.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. from datetime import timedelta
  2. from itertools import product
  3. import numpy as np
  4. import pytest
  5. from pandas._libs.tslibs import OutOfBoundsTimedelta
  6. from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
  7. from pandas import (
  8. NaT,
  9. Timedelta,
  10. offsets,
  11. to_timedelta,
  12. )
  13. def test_construct_with_weeks_unit_overflow():
  14. # GH#47268 don't silently wrap around
  15. with pytest.raises(OutOfBoundsTimedelta, match="without overflow"):
  16. Timedelta(1000000000000000000, unit="W")
  17. with pytest.raises(OutOfBoundsTimedelta, match="without overflow"):
  18. Timedelta(1000000000000000000.0, unit="W")
  19. def test_construct_from_td64_with_unit():
  20. # ignore the unit, as it may cause silently overflows leading to incorrect
  21. # results, and in non-overflow cases is irrelevant GH#46827
  22. obj = np.timedelta64(123456789000000000, "h")
  23. with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
  24. Timedelta(obj, unit="ps")
  25. with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
  26. Timedelta(obj, unit="ns")
  27. with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
  28. Timedelta(obj)
  29. def test_from_td64_retain_resolution():
  30. # case where we retain millisecond resolution
  31. obj = np.timedelta64(12345, "ms")
  32. td = Timedelta(obj)
  33. assert td._value == obj.view("i8")
  34. assert td._creso == NpyDatetimeUnit.NPY_FR_ms.value
  35. # Case where we cast to nearest-supported reso
  36. obj2 = np.timedelta64(1234, "D")
  37. td2 = Timedelta(obj2)
  38. assert td2._creso == NpyDatetimeUnit.NPY_FR_s.value
  39. assert td2 == obj2
  40. assert td2.days == 1234
  41. # Case that _would_ overflow if we didn't support non-nano
  42. obj3 = np.timedelta64(1000000000000000000, "us")
  43. td3 = Timedelta(obj3)
  44. assert td3.total_seconds() == 1000000000000
  45. assert td3._creso == NpyDatetimeUnit.NPY_FR_us.value
  46. def test_from_pytimedelta_us_reso():
  47. # pytimedelta has microsecond resolution, so Timedelta(pytd) inherits that
  48. td = timedelta(days=4, minutes=3)
  49. result = Timedelta(td)
  50. assert result.to_pytimedelta() == td
  51. assert result._creso == NpyDatetimeUnit.NPY_FR_us.value
  52. def test_from_tick_reso():
  53. tick = offsets.Nano()
  54. assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_ns.value
  55. tick = offsets.Micro()
  56. assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_us.value
  57. tick = offsets.Milli()
  58. assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_ms.value
  59. tick = offsets.Second()
  60. assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
  61. # everything above Second gets cast to the closest supported reso: second
  62. tick = offsets.Minute()
  63. assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
  64. tick = offsets.Hour()
  65. assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
  66. tick = offsets.Day()
  67. assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
  68. def test_construction():
  69. expected = np.timedelta64(10, "D").astype("m8[ns]").view("i8")
  70. assert Timedelta(10, unit="d")._value == expected
  71. assert Timedelta(10.0, unit="d")._value == expected
  72. assert Timedelta("10 days")._value == expected
  73. assert Timedelta(days=10)._value == expected
  74. assert Timedelta(days=10.0)._value == expected
  75. expected += np.timedelta64(10, "s").astype("m8[ns]").view("i8")
  76. assert Timedelta("10 days 00:00:10")._value == expected
  77. assert Timedelta(days=10, seconds=10)._value == expected
  78. assert Timedelta(days=10, milliseconds=10 * 1000)._value == expected
  79. assert Timedelta(days=10, microseconds=10 * 1000 * 1000)._value == expected
  80. # rounding cases
  81. assert Timedelta(82739999850000)._value == 82739999850000
  82. assert "0 days 22:58:59.999850" in str(Timedelta(82739999850000))
  83. assert Timedelta(123072001000000)._value == 123072001000000
  84. assert "1 days 10:11:12.001" in str(Timedelta(123072001000000))
  85. # string conversion with/without leading zero
  86. # GH#9570
  87. assert Timedelta("0:00:00") == timedelta(hours=0)
  88. assert Timedelta("00:00:00") == timedelta(hours=0)
  89. assert Timedelta("-1:00:00") == -timedelta(hours=1)
  90. assert Timedelta("-01:00:00") == -timedelta(hours=1)
  91. # more strings & abbrevs
  92. # GH#8190
  93. assert Timedelta("1 h") == timedelta(hours=1)
  94. assert Timedelta("1 hour") == timedelta(hours=1)
  95. assert Timedelta("1 hr") == timedelta(hours=1)
  96. assert Timedelta("1 hours") == timedelta(hours=1)
  97. assert Timedelta("-1 hours") == -timedelta(hours=1)
  98. assert Timedelta("1 m") == timedelta(minutes=1)
  99. assert Timedelta("1.5 m") == timedelta(seconds=90)
  100. assert Timedelta("1 minute") == timedelta(minutes=1)
  101. assert Timedelta("1 minutes") == timedelta(minutes=1)
  102. assert Timedelta("1 s") == timedelta(seconds=1)
  103. assert Timedelta("1 second") == timedelta(seconds=1)
  104. assert Timedelta("1 seconds") == timedelta(seconds=1)
  105. assert Timedelta("1 ms") == timedelta(milliseconds=1)
  106. assert Timedelta("1 milli") == timedelta(milliseconds=1)
  107. assert Timedelta("1 millisecond") == timedelta(milliseconds=1)
  108. assert Timedelta("1 us") == timedelta(microseconds=1)
  109. assert Timedelta("1 µs") == timedelta(microseconds=1)
  110. assert Timedelta("1 micros") == timedelta(microseconds=1)
  111. assert Timedelta("1 microsecond") == timedelta(microseconds=1)
  112. assert Timedelta("1.5 microsecond") == Timedelta("00:00:00.000001500")
  113. assert Timedelta("1 ns") == Timedelta("00:00:00.000000001")
  114. assert Timedelta("1 nano") == Timedelta("00:00:00.000000001")
  115. assert Timedelta("1 nanosecond") == Timedelta("00:00:00.000000001")
  116. # combos
  117. assert Timedelta("10 days 1 hour") == timedelta(days=10, hours=1)
  118. assert Timedelta("10 days 1 h") == timedelta(days=10, hours=1)
  119. assert Timedelta("10 days 1 h 1m 1s") == timedelta(
  120. days=10, hours=1, minutes=1, seconds=1
  121. )
  122. assert Timedelta("-10 days 1 h 1m 1s") == -timedelta(
  123. days=10, hours=1, minutes=1, seconds=1
  124. )
  125. assert Timedelta("-10 days 1 h 1m 1s") == -timedelta(
  126. days=10, hours=1, minutes=1, seconds=1
  127. )
  128. assert Timedelta("-10 days 1 h 1m 1s 3us") == -timedelta(
  129. days=10, hours=1, minutes=1, seconds=1, microseconds=3
  130. )
  131. assert Timedelta("-10 days 1 h 1.5m 1s 3us") == -timedelta(
  132. days=10, hours=1, minutes=1, seconds=31, microseconds=3
  133. )
  134. # Currently invalid as it has a - on the hh:mm:dd part
  135. # (only allowed on the days)
  136. msg = "only leading negative signs are allowed"
  137. with pytest.raises(ValueError, match=msg):
  138. Timedelta("-10 days -1 h 1.5m 1s 3us")
  139. # only leading neg signs are allowed
  140. with pytest.raises(ValueError, match=msg):
  141. Timedelta("10 days -1 h 1.5m 1s 3us")
  142. # no units specified
  143. msg = "no units specified"
  144. with pytest.raises(ValueError, match=msg):
  145. Timedelta("3.1415")
  146. # invalid construction
  147. msg = "cannot construct a Timedelta"
  148. with pytest.raises(ValueError, match=msg):
  149. Timedelta()
  150. msg = "unit abbreviation w/o a number"
  151. with pytest.raises(ValueError, match=msg):
  152. Timedelta("foo")
  153. msg = (
  154. "cannot construct a Timedelta from "
  155. "the passed arguments, allowed keywords are "
  156. )
  157. with pytest.raises(ValueError, match=msg):
  158. Timedelta(day=10)
  159. # floats
  160. expected = np.timedelta64(10, "s").astype("m8[ns]").view("i8") + np.timedelta64(
  161. 500, "ms"
  162. ).astype("m8[ns]").view("i8")
  163. assert Timedelta(10.5, unit="s")._value == expected
  164. # offset
  165. assert to_timedelta(offsets.Hour(2)) == Timedelta(hours=2)
  166. assert Timedelta(offsets.Hour(2)) == Timedelta(hours=2)
  167. assert Timedelta(offsets.Second(2)) == Timedelta(seconds=2)
  168. # GH#11995: unicode
  169. expected = Timedelta("1H")
  170. result = Timedelta("1H")
  171. assert result == expected
  172. assert to_timedelta(offsets.Hour(2)) == Timedelta("0 days, 02:00:00")
  173. msg = "unit abbreviation w/o a number"
  174. with pytest.raises(ValueError, match=msg):
  175. Timedelta("foo bar")
  176. @pytest.mark.parametrize(
  177. "item",
  178. list(
  179. {
  180. "days": "D",
  181. "seconds": "s",
  182. "microseconds": "us",
  183. "milliseconds": "ms",
  184. "minutes": "m",
  185. "hours": "h",
  186. "weeks": "W",
  187. }.items()
  188. ),
  189. )
  190. @pytest.mark.parametrize(
  191. "npdtype", [np.int64, np.int32, np.int16, np.float64, np.float32, np.float16]
  192. )
  193. def test_td_construction_with_np_dtypes(npdtype, item):
  194. # GH#8757: test construction with np dtypes
  195. pykwarg, npkwarg = item
  196. expected = np.timedelta64(1, npkwarg).astype("m8[ns]").view("i8")
  197. assert Timedelta(**{pykwarg: npdtype(1)})._value == expected
  198. @pytest.mark.parametrize(
  199. "val",
  200. [
  201. "1s",
  202. "-1s",
  203. "1us",
  204. "-1us",
  205. "1 day",
  206. "-1 day",
  207. "-23:59:59.999999",
  208. "-1 days +23:59:59.999999",
  209. "-1ns",
  210. "1ns",
  211. "-23:59:59.999999999",
  212. ],
  213. )
  214. def test_td_from_repr_roundtrip(val):
  215. # round-trip both for string and value
  216. td = Timedelta(val)
  217. assert Timedelta(td._value) == td
  218. assert Timedelta(str(td)) == td
  219. assert Timedelta(td._repr_base(format="all")) == td
  220. assert Timedelta(td._repr_base()) == td
  221. def test_overflow_on_construction():
  222. # GH#3374
  223. value = Timedelta("1day")._value * 20169940
  224. msg = "Cannot cast 1742682816000000000000 from ns to 'ns' without overflow"
  225. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  226. Timedelta(value)
  227. # xref GH#17637
  228. msg = "Cannot cast 139993 from D to 'ns' without overflow"
  229. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  230. Timedelta(7 * 19999, unit="D")
  231. # used to overflow before non-ns support
  232. td = Timedelta(timedelta(days=13 * 19999))
  233. assert td._creso == NpyDatetimeUnit.NPY_FR_us.value
  234. assert td.days == 13 * 19999
  235. @pytest.mark.parametrize(
  236. "val, unit",
  237. [
  238. (3508, "M"),
  239. (15251, "W"), # 1
  240. (106752, "D"), # change from previous:
  241. (2562048, "h"), # 0 hours
  242. (153722868, "m"), # 13 minutes
  243. (9223372037, "s"), # 44 seconds
  244. ],
  245. )
  246. def test_construction_out_of_bounds_td64ns(val, unit):
  247. # TODO: parametrize over units just above/below the implementation bounds
  248. # once GH#38964 is resolved
  249. # Timedelta.max is just under 106752 days
  250. td64 = np.timedelta64(val, unit)
  251. assert td64.astype("m8[ns]").view("i8") < 0 # i.e. naive astype will be wrong
  252. td = Timedelta(td64)
  253. if unit != "M":
  254. # with unit="M" the conversion to "s" is poorly defined
  255. # (and numpy issues DeprecationWarning)
  256. assert td.asm8 == td64
  257. assert td.asm8.dtype == "m8[s]"
  258. msg = r"Cannot cast 1067\d\d days .* to unit='ns' without overflow"
  259. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  260. td.as_unit("ns")
  261. # But just back in bounds and we are OK
  262. assert Timedelta(td64 - 1) == td64 - 1
  263. td64 *= -1
  264. assert td64.astype("m8[ns]").view("i8") > 0 # i.e. naive astype will be wrong
  265. td2 = Timedelta(td64)
  266. msg = r"Cannot cast -1067\d\d days .* to unit='ns' without overflow"
  267. with pytest.raises(OutOfBoundsTimedelta, match=msg):
  268. td2.as_unit("ns")
  269. # But just back in bounds and we are OK
  270. assert Timedelta(td64 + 1) == td64 + 1
  271. @pytest.mark.parametrize(
  272. "val, unit",
  273. [
  274. (3508 * 10**9, "M"),
  275. (15251 * 10**9, "W"),
  276. (106752 * 10**9, "D"),
  277. (2562048 * 10**9, "h"),
  278. (153722868 * 10**9, "m"),
  279. ],
  280. )
  281. def test_construction_out_of_bounds_td64s(val, unit):
  282. td64 = np.timedelta64(val, unit)
  283. with pytest.raises(OutOfBoundsTimedelta, match=str(td64)):
  284. Timedelta(td64)
  285. # But just back in bounds and we are OK
  286. assert Timedelta(td64 - 10**9) == td64 - 10**9
  287. @pytest.mark.parametrize(
  288. "fmt,exp",
  289. [
  290. (
  291. "P6DT0H50M3.010010012S",
  292. Timedelta(
  293. days=6,
  294. minutes=50,
  295. seconds=3,
  296. milliseconds=10,
  297. microseconds=10,
  298. nanoseconds=12,
  299. ),
  300. ),
  301. (
  302. "P-6DT0H50M3.010010012S",
  303. Timedelta(
  304. days=-6,
  305. minutes=50,
  306. seconds=3,
  307. milliseconds=10,
  308. microseconds=10,
  309. nanoseconds=12,
  310. ),
  311. ),
  312. ("P4DT12H30M5S", Timedelta(days=4, hours=12, minutes=30, seconds=5)),
  313. ("P0DT0H0M0.000000123S", Timedelta(nanoseconds=123)),
  314. ("P0DT0H0M0.00001S", Timedelta(microseconds=10)),
  315. ("P0DT0H0M0.001S", Timedelta(milliseconds=1)),
  316. ("P0DT0H1M0S", Timedelta(minutes=1)),
  317. ("P1DT25H61M61S", Timedelta(days=1, hours=25, minutes=61, seconds=61)),
  318. ("PT1S", Timedelta(seconds=1)),
  319. ("PT0S", Timedelta(seconds=0)),
  320. ("P1WT0S", Timedelta(days=7, seconds=0)),
  321. ("P1D", Timedelta(days=1)),
  322. ("P1DT1H", Timedelta(days=1, hours=1)),
  323. ("P1W", Timedelta(days=7)),
  324. ("PT300S", Timedelta(seconds=300)),
  325. ("P1DT0H0M00000000000S", Timedelta(days=1)),
  326. ("PT-6H3M", Timedelta(hours=-6, minutes=3)),
  327. ("-PT6H3M", Timedelta(hours=-6, minutes=-3)),
  328. ("-PT-6H+3M", Timedelta(hours=6, minutes=-3)),
  329. ],
  330. )
  331. def test_iso_constructor(fmt, exp):
  332. assert Timedelta(fmt) == exp
  333. @pytest.mark.parametrize(
  334. "fmt",
  335. [
  336. "PPPPPPPPPPPP",
  337. "PDTHMS",
  338. "P0DT999H999M999S",
  339. "P1DT0H0M0.0000000000000S",
  340. "P1DT0H0M0.S",
  341. "P",
  342. "-P",
  343. ],
  344. )
  345. def test_iso_constructor_raises(fmt):
  346. msg = f"Invalid ISO 8601 Duration format - {fmt}"
  347. with pytest.raises(ValueError, match=msg):
  348. Timedelta(fmt)
  349. @pytest.mark.parametrize(
  350. "constructed_td, conversion",
  351. [
  352. (Timedelta(nanoseconds=100), "100ns"),
  353. (
  354. Timedelta(
  355. days=1,
  356. hours=1,
  357. minutes=1,
  358. weeks=1,
  359. seconds=1,
  360. milliseconds=1,
  361. microseconds=1,
  362. nanoseconds=1,
  363. ),
  364. 694861001001001,
  365. ),
  366. (Timedelta(microseconds=1) + Timedelta(nanoseconds=1), "1us1ns"),
  367. (Timedelta(microseconds=1) - Timedelta(nanoseconds=1), "999ns"),
  368. (Timedelta(microseconds=1) + 5 * Timedelta(nanoseconds=-2), "990ns"),
  369. ],
  370. )
  371. def test_td_constructor_on_nanoseconds(constructed_td, conversion):
  372. # GH#9273
  373. assert constructed_td == Timedelta(conversion)
  374. def test_td_constructor_value_error():
  375. msg = "Invalid type <class 'str'>. Must be int or float."
  376. with pytest.raises(TypeError, match=msg):
  377. Timedelta(nanoseconds="abc")
  378. def test_timedelta_constructor_identity():
  379. # Test for #30543
  380. expected = Timedelta(np.timedelta64(1, "s"))
  381. result = Timedelta(expected)
  382. assert result is expected
  383. def test_timedelta_pass_td_and_kwargs_raises():
  384. # don't silently ignore the kwargs GH#48898
  385. td = Timedelta(days=1)
  386. msg = (
  387. "Cannot pass both a Timedelta input and timedelta keyword arguments, "
  388. r"got \['days'\]"
  389. )
  390. with pytest.raises(ValueError, match=msg):
  391. Timedelta(td, days=2)
  392. @pytest.mark.parametrize(
  393. "constructor, value, unit, expectation",
  394. [
  395. (Timedelta, "10s", "ms", (ValueError, "unit must not be specified")),
  396. (to_timedelta, "10s", "ms", (ValueError, "unit must not be specified")),
  397. (to_timedelta, ["1", 2, 3], "s", (ValueError, "unit must not be specified")),
  398. ],
  399. )
  400. def test_string_with_unit(constructor, value, unit, expectation):
  401. exp, match = expectation
  402. with pytest.raises(exp, match=match):
  403. _ = constructor(value, unit=unit)
  404. @pytest.mark.parametrize(
  405. "value",
  406. [
  407. "".join(elements)
  408. for repetition in (1, 2)
  409. for elements in product("+-, ", repeat=repetition)
  410. ],
  411. )
  412. def test_string_without_numbers(value):
  413. # GH39710 Timedelta input string with only symbols and no digits raises an error
  414. msg = (
  415. "symbols w/o a number"
  416. if value != "--"
  417. else "only leading negative signs are allowed"
  418. )
  419. with pytest.raises(ValueError, match=msg):
  420. Timedelta(value)
  421. def test_timedelta_new_npnat():
  422. # GH#48898
  423. nat = np.timedelta64("NaT", "h")
  424. assert Timedelta(nat) is NaT
  425. def test_subclass_respected():
  426. # GH#49579
  427. class MyCustomTimedelta(Timedelta):
  428. pass
  429. td = MyCustomTimedelta("1 minute")
  430. assert isinstance(td, MyCustomTimedelta)
  431. def test_non_nano_value():
  432. # https://github.com/pandas-dev/pandas/issues/49076
  433. result = Timedelta(10, unit="D").as_unit("s").value
  434. # `.value` shows nanoseconds, even though unit is 's'
  435. assert result == 864000000000000
  436. # out-of-nanoseconds-bounds `.value` raises informative message
  437. msg = (
  438. r"Cannot convert Timedelta to nanoseconds without overflow. "
  439. r"Use `.asm8.view\('i8'\)` to cast represent Timedelta in its "
  440. r"own unit \(here, s\).$"
  441. )
  442. td = Timedelta(1_000, "D").as_unit("s") * 1_000
  443. with pytest.raises(OverflowError, match=msg):
  444. td.value
  445. # check that the suggested workaround actually works
  446. result = td.asm8.view("i8")
  447. assert result == 86400000000