test_nat.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. from datetime import datetime, timedelta
  2. import numpy as np
  3. import pytest
  4. import pytz
  5. from pandas._libs.tslibs import iNaT
  6. import pandas.compat as compat
  7. from pandas import (
  8. DatetimeIndex, Index, NaT, Period, Series, Timedelta, TimedeltaIndex,
  9. Timestamp)
  10. from pandas.core.arrays import PeriodArray
  11. from pandas.util import testing as tm
  12. @pytest.mark.parametrize("nat,idx", [(Timestamp("NaT"), DatetimeIndex),
  13. (Timedelta("NaT"), TimedeltaIndex),
  14. (Period("NaT", freq="M"), PeriodArray)])
  15. def test_nat_fields(nat, idx):
  16. for field in idx._field_ops:
  17. # weekday is a property of DTI, but a method
  18. # on NaT/Timestamp for compat with datetime
  19. if field == "weekday":
  20. continue
  21. result = getattr(NaT, field)
  22. assert np.isnan(result)
  23. result = getattr(nat, field)
  24. assert np.isnan(result)
  25. for field in idx._bool_ops:
  26. result = getattr(NaT, field)
  27. assert result is False
  28. result = getattr(nat, field)
  29. assert result is False
  30. def test_nat_vector_field_access():
  31. idx = DatetimeIndex(["1/1/2000", None, None, "1/4/2000"])
  32. for field in DatetimeIndex._field_ops:
  33. # weekday is a property of DTI, but a method
  34. # on NaT/Timestamp for compat with datetime
  35. if field == "weekday":
  36. continue
  37. result = getattr(idx, field)
  38. expected = Index([getattr(x, field) for x in idx])
  39. tm.assert_index_equal(result, expected)
  40. ser = Series(idx)
  41. for field in DatetimeIndex._field_ops:
  42. # weekday is a property of DTI, but a method
  43. # on NaT/Timestamp for compat with datetime
  44. if field == "weekday":
  45. continue
  46. result = getattr(ser.dt, field)
  47. expected = [getattr(x, field) for x in idx]
  48. tm.assert_series_equal(result, Series(expected))
  49. for field in DatetimeIndex._bool_ops:
  50. result = getattr(ser.dt, field)
  51. expected = [getattr(x, field) for x in idx]
  52. tm.assert_series_equal(result, Series(expected))
  53. @pytest.mark.parametrize("klass", [Timestamp, Timedelta, Period])
  54. @pytest.mark.parametrize("value", [None, np.nan, iNaT, float("nan"),
  55. NaT, "NaT", "nat"])
  56. def test_identity(klass, value):
  57. assert klass(value) is NaT
  58. @pytest.mark.parametrize("klass", [Timestamp, Timedelta, Period])
  59. @pytest.mark.parametrize("value", ["", "nat", "NAT", None, np.nan])
  60. def test_equality(klass, value):
  61. if klass is Period and value == "":
  62. pytest.skip("Period cannot parse empty string")
  63. assert klass(value).value == iNaT
  64. @pytest.mark.parametrize("klass", [Timestamp, Timedelta])
  65. @pytest.mark.parametrize("method", ["round", "floor", "ceil"])
  66. @pytest.mark.parametrize("freq", ["s", "5s", "min", "5min", "h", "5h"])
  67. def test_round_nat(klass, method, freq):
  68. # see gh-14940
  69. ts = klass("nat")
  70. round_method = getattr(ts, method)
  71. assert round_method(freq) is ts
  72. @pytest.mark.parametrize("method", [
  73. "astimezone", "combine", "ctime", "dst", "fromordinal",
  74. "fromtimestamp", "isocalendar", "strftime", "strptime",
  75. "time", "timestamp", "timetuple", "timetz", "toordinal",
  76. "tzname", "utcfromtimestamp", "utcnow", "utcoffset",
  77. "utctimetuple", "timestamp"
  78. ])
  79. def test_nat_methods_raise(method):
  80. # see gh-9513, gh-17329
  81. msg = "NaTType does not support {method}".format(method=method)
  82. with pytest.raises(ValueError, match=msg):
  83. getattr(NaT, method)()
  84. @pytest.mark.parametrize("method", [
  85. "weekday", "isoweekday"
  86. ])
  87. def test_nat_methods_nan(method):
  88. # see gh-9513, gh-17329
  89. assert np.isnan(getattr(NaT, method)())
  90. @pytest.mark.parametrize("method", [
  91. "date", "now", "replace", "today",
  92. "tz_convert", "tz_localize"
  93. ])
  94. def test_nat_methods_nat(method):
  95. # see gh-8254, gh-9513, gh-17329
  96. assert getattr(NaT, method)() is NaT
  97. @pytest.mark.parametrize("get_nat", [
  98. lambda x: NaT,
  99. lambda x: Timedelta(x),
  100. lambda x: Timestamp(x)
  101. ])
  102. def test_nat_iso_format(get_nat):
  103. # see gh-12300
  104. assert get_nat("NaT").isoformat() == "NaT"
  105. @pytest.mark.parametrize("klass,expected", [
  106. (Timestamp, ["freqstr", "normalize", "to_julian_date", "to_period", "tz"]),
  107. (Timedelta, ["components", "delta", "is_populated", "to_pytimedelta",
  108. "to_timedelta64", "view"])
  109. ])
  110. def test_missing_public_nat_methods(klass, expected):
  111. # see gh-17327
  112. #
  113. # NaT should have *most* of the Timestamp and Timedelta methods.
  114. # Here, we check which public methods NaT does not have. We
  115. # ignore any missing private methods.
  116. nat_names = dir(NaT)
  117. klass_names = dir(klass)
  118. missing = [x for x in klass_names if x not in nat_names and
  119. not x.startswith("_")]
  120. missing.sort()
  121. assert missing == expected
  122. def _get_overlap_public_nat_methods(klass, as_tuple=False):
  123. """
  124. Get overlapping public methods between NaT and another class.
  125. Parameters
  126. ----------
  127. klass : type
  128. The class to compare with NaT
  129. as_tuple : bool, default False
  130. Whether to return a list of tuples of the form (klass, method).
  131. Returns
  132. -------
  133. overlap : list
  134. """
  135. nat_names = dir(NaT)
  136. klass_names = dir(klass)
  137. overlap = [x for x in nat_names if x in klass_names and
  138. not x.startswith("_") and
  139. callable(getattr(klass, x))]
  140. # Timestamp takes precedence over Timedelta in terms of overlap.
  141. if klass is Timedelta:
  142. ts_names = dir(Timestamp)
  143. overlap = [x for x in overlap if x not in ts_names]
  144. if as_tuple:
  145. overlap = [(klass, method) for method in overlap]
  146. overlap.sort()
  147. return overlap
  148. @pytest.mark.parametrize("klass,expected", [
  149. (Timestamp, ["astimezone", "ceil", "combine", "ctime", "date", "day_name",
  150. "dst", "floor", "fromisoformat", "fromordinal",
  151. "fromtimestamp", "isocalendar", "isoformat", "isoweekday",
  152. "month_name", "now", "replace", "round", "strftime",
  153. "strptime", "time", "timestamp", "timetuple", "timetz",
  154. "to_datetime64", "to_pydatetime", "today", "toordinal",
  155. "tz_convert", "tz_localize", "tzname", "utcfromtimestamp",
  156. "utcnow", "utcoffset", "utctimetuple", "weekday"]),
  157. (Timedelta, ["total_seconds"])
  158. ])
  159. def test_overlap_public_nat_methods(klass, expected):
  160. # see gh-17327
  161. #
  162. # NaT should have *most* of the Timestamp and Timedelta methods.
  163. # In case when Timestamp, Timedelta, and NaT are overlap, the overlap
  164. # is considered to be with Timestamp and NaT, not Timedelta.
  165. # "fromisoformat" was introduced in 3.7
  166. if klass is Timestamp and not compat.PY37:
  167. expected.remove("fromisoformat")
  168. assert _get_overlap_public_nat_methods(klass) == expected
  169. @pytest.mark.parametrize("compare", (
  170. _get_overlap_public_nat_methods(Timestamp, True) +
  171. _get_overlap_public_nat_methods(Timedelta, True))
  172. )
  173. def test_nat_doc_strings(compare):
  174. # see gh-17327
  175. #
  176. # The docstrings for overlapping methods should match.
  177. klass, method = compare
  178. klass_doc = getattr(klass, method).__doc__
  179. nat_doc = getattr(NaT, method).__doc__
  180. assert klass_doc == nat_doc
  181. _ops = {
  182. "left_plus_right": lambda a, b: a + b,
  183. "right_plus_left": lambda a, b: b + a,
  184. "left_minus_right": lambda a, b: a - b,
  185. "right_minus_left": lambda a, b: b - a,
  186. "left_times_right": lambda a, b: a * b,
  187. "right_times_left": lambda a, b: b * a,
  188. "left_div_right": lambda a, b: a / b,
  189. "right_div_left": lambda a, b: b / a,
  190. }
  191. @pytest.mark.parametrize("op_name", list(_ops.keys()))
  192. @pytest.mark.parametrize("value,val_type", [
  193. (2, "scalar"),
  194. (1.5, "scalar"),
  195. (np.nan, "scalar"),
  196. (timedelta(3600), "timedelta"),
  197. (Timedelta("5s"), "timedelta"),
  198. (datetime(2014, 1, 1), "timestamp"),
  199. (Timestamp("2014-01-01"), "timestamp"),
  200. (Timestamp("2014-01-01", tz="UTC"), "timestamp"),
  201. (Timestamp("2014-01-01", tz="US/Eastern"), "timestamp"),
  202. (pytz.timezone("Asia/Tokyo").localize(datetime(2014, 1, 1)), "timestamp"),
  203. ])
  204. def test_nat_arithmetic_scalar(op_name, value, val_type):
  205. # see gh-6873
  206. invalid_ops = {
  207. "scalar": {"right_div_left"},
  208. "timedelta": {"left_times_right", "right_times_left"},
  209. "timestamp": {"left_times_right", "right_times_left",
  210. "left_div_right", "right_div_left"}
  211. }
  212. op = _ops[op_name]
  213. if op_name in invalid_ops.get(val_type, set()):
  214. if (val_type == "timedelta" and "times" in op_name and
  215. isinstance(value, Timedelta)):
  216. msg = "Cannot multiply"
  217. else:
  218. msg = "unsupported operand type"
  219. with pytest.raises(TypeError, match=msg):
  220. op(NaT, value)
  221. else:
  222. if val_type == "timedelta" and "div" in op_name:
  223. expected = np.nan
  224. else:
  225. expected = NaT
  226. assert op(NaT, value) is expected
  227. @pytest.mark.parametrize("val,expected", [
  228. (np.nan, NaT),
  229. (NaT, np.nan),
  230. (np.timedelta64("NaT"), np.nan)
  231. ])
  232. def test_nat_rfloordiv_timedelta(val, expected):
  233. # see gh-#18846
  234. #
  235. # See also test_timedelta.TestTimedeltaArithmetic.test_floordiv
  236. td = Timedelta(hours=3, minutes=4)
  237. assert td // val is expected
  238. @pytest.mark.parametrize("op_name", [
  239. "left_plus_right", "right_plus_left",
  240. "left_minus_right", "right_minus_left"
  241. ])
  242. @pytest.mark.parametrize("value", [
  243. DatetimeIndex(["2011-01-01", "2011-01-02"], name="x"),
  244. DatetimeIndex(["2011-01-01", "2011-01-02"], name="x"),
  245. TimedeltaIndex(["1 day", "2 day"], name="x"),
  246. ])
  247. def test_nat_arithmetic_index(op_name, value):
  248. # see gh-11718
  249. exp_name = "x"
  250. exp_data = [NaT] * 2
  251. if isinstance(value, DatetimeIndex) and "plus" in op_name:
  252. expected = DatetimeIndex(exp_data, name=exp_name, tz=value.tz)
  253. else:
  254. expected = TimedeltaIndex(exp_data, name=exp_name)
  255. tm.assert_index_equal(_ops[op_name](NaT, value), expected)
  256. @pytest.mark.parametrize("op_name", [
  257. "left_plus_right", "right_plus_left",
  258. "left_minus_right", "right_minus_left"
  259. ])
  260. @pytest.mark.parametrize("box", [TimedeltaIndex, Series])
  261. def test_nat_arithmetic_td64_vector(op_name, box):
  262. # see gh-19124
  263. vec = box(["1 day", "2 day"], dtype="timedelta64[ns]")
  264. box_nat = box([NaT, NaT], dtype="timedelta64[ns]")
  265. tm.assert_equal(_ops[op_name](vec, NaT), box_nat)
  266. def test_nat_pinned_docstrings():
  267. # see gh-17327
  268. assert NaT.ctime.__doc__ == datetime.ctime.__doc__