conversion.pyx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. import numpy as np
  2. cimport numpy as cnp
  3. from libc.math cimport log10
  4. from numpy cimport (
  5. int32_t,
  6. int64_t,
  7. )
  8. cnp.import_array()
  9. # stdlib datetime imports
  10. from datetime import timezone
  11. from cpython.datetime cimport (
  12. PyDate_Check,
  13. PyDateTime_Check,
  14. datetime,
  15. import_datetime,
  16. time,
  17. timedelta,
  18. tzinfo,
  19. )
  20. import_datetime()
  21. from pandas._libs.tslibs.base cimport ABCTimestamp
  22. from pandas._libs.tslibs.dtypes cimport (
  23. abbrev_to_npy_unit,
  24. get_supported_reso,
  25. periods_per_second,
  26. )
  27. from pandas._libs.tslibs.np_datetime cimport (
  28. NPY_DATETIMEUNIT,
  29. NPY_FR_ns,
  30. NPY_FR_us,
  31. check_dts_bounds,
  32. convert_reso,
  33. get_datetime64_unit,
  34. get_datetime64_value,
  35. get_implementation_bounds,
  36. npy_datetime,
  37. npy_datetimestruct,
  38. npy_datetimestruct_to_datetime,
  39. pandas_datetime_to_datetimestruct,
  40. pydatetime_to_dt64,
  41. pydatetime_to_dtstruct,
  42. string_to_dts,
  43. )
  44. from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime
  45. from pandas._libs.tslibs.nattype cimport (
  46. NPY_NAT,
  47. c_NaT as NaT,
  48. c_nat_strings as nat_strings,
  49. )
  50. from pandas._libs.tslibs.parsing cimport parse_datetime_string
  51. from pandas._libs.tslibs.timestamps cimport _Timestamp
  52. from pandas._libs.tslibs.timezones cimport (
  53. get_utcoffset,
  54. is_utc,
  55. )
  56. from pandas._libs.tslibs.tzconversion cimport (
  57. Localizer,
  58. tz_localize_to_utc_single,
  59. )
  60. from pandas._libs.tslibs.util cimport (
  61. is_datetime64_object,
  62. is_float_object,
  63. is_integer_object,
  64. )
  65. # ----------------------------------------------------------------------
  66. # Constants
  67. DT64NS_DTYPE = np.dtype("M8[ns]")
  68. TD64NS_DTYPE = np.dtype("m8[ns]")
  69. # ----------------------------------------------------------------------
  70. # Unit Conversion Helpers
  71. cdef int64_t cast_from_unit(
  72. object ts,
  73. str unit,
  74. NPY_DATETIMEUNIT out_reso=NPY_FR_ns
  75. ) except? -1:
  76. """
  77. Return a casting of the unit represented to nanoseconds
  78. round the fractional part of a float to our precision, p.
  79. Parameters
  80. ----------
  81. ts : int, float, or None
  82. unit : str
  83. Returns
  84. -------
  85. int64_t
  86. """
  87. cdef:
  88. int64_t m
  89. int p
  90. m, p = precision_from_unit(unit, out_reso)
  91. # just give me the unit back
  92. if ts is None:
  93. return m
  94. if unit in ["Y", "M"]:
  95. if is_float_object(ts) and not ts.is_integer():
  96. # GH#47267 it is clear that 2 "M" corresponds to 1970-02-01,
  97. # but not clear what 2.5 "M" corresponds to, so we will
  98. # disallow that case.
  99. raise ValueError(
  100. f"Conversion of non-round float with unit={unit} "
  101. "is ambiguous"
  102. )
  103. # GH#47266 go through np.datetime64 to avoid weird results e.g. with "Y"
  104. # and 150 we'd get 2120-01-01 09:00:00
  105. if is_float_object(ts):
  106. ts = int(ts)
  107. dt64obj = np.datetime64(ts, unit)
  108. return get_datetime64_nanos(dt64obj, out_reso)
  109. # cast the unit, multiply base/frac separately
  110. # to avoid precision issues from float -> int
  111. try:
  112. base = <int64_t>ts
  113. except OverflowError as err:
  114. raise OutOfBoundsDatetime(
  115. f"cannot convert input {ts} with the unit '{unit}'"
  116. ) from err
  117. frac = ts - base
  118. if p:
  119. frac = round(frac, p)
  120. try:
  121. return <int64_t>(base * m) + <int64_t>(frac * m)
  122. except OverflowError as err:
  123. raise OutOfBoundsDatetime(
  124. f"cannot convert input {ts} with the unit '{unit}'"
  125. ) from err
  126. cpdef inline (int64_t, int) precision_from_unit(
  127. str unit,
  128. NPY_DATETIMEUNIT out_reso=NPY_DATETIMEUNIT.NPY_FR_ns,
  129. ):
  130. """
  131. Return a casting of the unit represented to nanoseconds + the precision
  132. to round the fractional part.
  133. Notes
  134. -----
  135. The caller is responsible for ensuring that the default value of "ns"
  136. takes the place of None.
  137. """
  138. cdef:
  139. int64_t m
  140. int64_t multiplier
  141. int p
  142. NPY_DATETIMEUNIT reso = abbrev_to_npy_unit(unit)
  143. multiplier = periods_per_second(out_reso)
  144. if reso == NPY_DATETIMEUNIT.NPY_FR_Y:
  145. # each 400 years we have 97 leap years, for an average of 97/400=.2425
  146. # extra days each year. We get 31556952 by writing
  147. # 3600*24*365.2425=31556952
  148. m = multiplier * 31556952
  149. elif reso == NPY_DATETIMEUNIT.NPY_FR_M:
  150. # 2629746 comes from dividing the "Y" case by 12.
  151. m = multiplier * 2629746
  152. elif reso == NPY_DATETIMEUNIT.NPY_FR_W:
  153. m = multiplier * 3600 * 24 * 7
  154. elif reso == NPY_DATETIMEUNIT.NPY_FR_D:
  155. m = multiplier * 3600 * 24
  156. elif reso == NPY_DATETIMEUNIT.NPY_FR_h:
  157. m = multiplier * 3600
  158. elif reso == NPY_DATETIMEUNIT.NPY_FR_m:
  159. m = multiplier * 60
  160. elif reso == NPY_DATETIMEUNIT.NPY_FR_s:
  161. m = multiplier
  162. elif reso == NPY_DATETIMEUNIT.NPY_FR_ms:
  163. m = multiplier // 1_000
  164. elif reso == NPY_DATETIMEUNIT.NPY_FR_us:
  165. m = multiplier // 1_000_000
  166. elif reso == NPY_DATETIMEUNIT.NPY_FR_ns or reso == NPY_DATETIMEUNIT.NPY_FR_GENERIC:
  167. m = multiplier // 1_000_000_000
  168. else:
  169. raise ValueError(f"cannot cast unit {unit}")
  170. p = <int>log10(m) # number of digits in 'm' minus 1
  171. return m, p
  172. cdef int64_t get_datetime64_nanos(object val, NPY_DATETIMEUNIT reso) except? -1:
  173. """
  174. Extract the value and unit from a np.datetime64 object, then convert the
  175. value to nanoseconds if necessary.
  176. """
  177. cdef:
  178. npy_datetimestruct dts
  179. NPY_DATETIMEUNIT unit
  180. npy_datetime ival
  181. ival = get_datetime64_value(val)
  182. if ival == NPY_NAT:
  183. return NPY_NAT
  184. unit = get_datetime64_unit(val)
  185. if unit != reso:
  186. pandas_datetime_to_datetimestruct(ival, unit, &dts)
  187. check_dts_bounds(&dts, reso)
  188. ival = npy_datetimestruct_to_datetime(reso, &dts)
  189. return ival
  190. # ----------------------------------------------------------------------
  191. # _TSObject Conversion
  192. # lightweight C object to hold datetime & int64 pair
  193. cdef class _TSObject:
  194. # cdef:
  195. # npy_datetimestruct dts # npy_datetimestruct
  196. # int64_t value # numpy dt64
  197. # tzinfo tzinfo
  198. # bint fold
  199. # NPY_DATETIMEUNIT creso
  200. def __cinit__(self):
  201. # GH 25057. As per PEP 495, set fold to 0 by default
  202. self.fold = 0
  203. self.creso = NPY_FR_ns # default value
  204. cdef int64_t ensure_reso(self, NPY_DATETIMEUNIT creso, str val=None) except? -1:
  205. if self.creso != creso:
  206. try:
  207. self.value = convert_reso(self.value, self.creso, creso, False)
  208. except OverflowError as err:
  209. if val is not None:
  210. raise OutOfBoundsDatetime(
  211. f"Out of bounds nanosecond timestamp: {val}"
  212. ) from err
  213. raise OutOfBoundsDatetime from err
  214. self.creso = creso
  215. return self.value
  216. cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit,
  217. bint dayfirst, bint yearfirst, int32_t nanos=0):
  218. """
  219. Extract datetime and int64 from any of:
  220. - np.int64 (with unit providing a possible modifier)
  221. - np.datetime64
  222. - a float (with unit providing a possible modifier)
  223. - python int or long object (with unit providing a possible modifier)
  224. - iso8601 string object
  225. - python datetime object
  226. - another timestamp object
  227. Raises
  228. ------
  229. OutOfBoundsDatetime : ts cannot be converted within implementation bounds
  230. """
  231. cdef:
  232. _TSObject obj
  233. NPY_DATETIMEUNIT reso
  234. obj = _TSObject()
  235. if isinstance(ts, str):
  236. return convert_str_to_tsobject(ts, tz, unit, dayfirst, yearfirst)
  237. if ts is None or ts is NaT:
  238. obj.value = NPY_NAT
  239. elif is_datetime64_object(ts):
  240. reso = get_supported_reso(get_datetime64_unit(ts))
  241. obj.creso = reso
  242. obj.value = get_datetime64_nanos(ts, reso)
  243. if obj.value != NPY_NAT:
  244. pandas_datetime_to_datetimestruct(obj.value, reso, &obj.dts)
  245. elif is_integer_object(ts):
  246. try:
  247. ts = <int64_t>ts
  248. except OverflowError:
  249. # GH#26651 re-raise as OutOfBoundsDatetime
  250. raise OutOfBoundsDatetime(f"Out of bounds nanosecond timestamp {ts}")
  251. if ts == NPY_NAT:
  252. obj.value = NPY_NAT
  253. else:
  254. if unit is None:
  255. unit = "ns"
  256. in_reso = abbrev_to_npy_unit(unit)
  257. reso = get_supported_reso(in_reso)
  258. ts = cast_from_unit(ts, unit, reso)
  259. obj.value = ts
  260. obj.creso = reso
  261. pandas_datetime_to_datetimestruct(ts, reso, &obj.dts)
  262. elif is_float_object(ts):
  263. if ts != ts or ts == NPY_NAT:
  264. obj.value = NPY_NAT
  265. else:
  266. ts = cast_from_unit(ts, unit)
  267. obj.value = ts
  268. pandas_datetime_to_datetimestruct(ts, NPY_FR_ns, &obj.dts)
  269. elif PyDateTime_Check(ts):
  270. if nanos == 0:
  271. if isinstance(ts, ABCTimestamp):
  272. reso = abbrev_to_npy_unit(ts.unit) # TODO: faster way to do this?
  273. else:
  274. # TODO: what if user explicitly passes nanos=0?
  275. reso = NPY_FR_us
  276. else:
  277. reso = NPY_FR_ns
  278. return convert_datetime_to_tsobject(ts, tz, nanos, reso=reso)
  279. elif PyDate_Check(ts):
  280. # Keep the converter same as PyDateTime's
  281. # For date object we give the lowest supported resolution, i.e. "s"
  282. ts = datetime.combine(ts, time())
  283. return convert_datetime_to_tsobject(
  284. ts, tz, nanos=0, reso=NPY_DATETIMEUNIT.NPY_FR_s
  285. )
  286. else:
  287. from .period import Period
  288. if isinstance(ts, Period):
  289. raise ValueError("Cannot convert Period to Timestamp "
  290. "unambiguously. Use to_timestamp")
  291. raise TypeError(f"Cannot convert input [{ts}] of type {type(ts)} to "
  292. f"Timestamp")
  293. maybe_localize_tso(obj, tz, obj.creso)
  294. return obj
  295. cdef maybe_localize_tso(_TSObject obj, tzinfo tz, NPY_DATETIMEUNIT reso):
  296. if tz is not None:
  297. _localize_tso(obj, tz, reso)
  298. if obj.value != NPY_NAT:
  299. # check_overflows needs to run after _localize_tso
  300. check_dts_bounds(&obj.dts, reso)
  301. check_overflows(obj, reso)
  302. cdef _TSObject convert_datetime_to_tsobject(
  303. datetime ts,
  304. tzinfo tz,
  305. int32_t nanos=0,
  306. NPY_DATETIMEUNIT reso=NPY_FR_ns,
  307. ):
  308. """
  309. Convert a datetime (or Timestamp) input `ts`, along with optional timezone
  310. object `tz` to a _TSObject.
  311. The optional argument `nanos` allows for cases where datetime input
  312. needs to be supplemented with higher-precision information.
  313. Parameters
  314. ----------
  315. ts : datetime or Timestamp
  316. Value to be converted to _TSObject
  317. tz : tzinfo or None
  318. timezone for the timezone-aware output
  319. nanos : int32_t, default is 0
  320. nanoseconds supplement the precision of the datetime input ts
  321. reso : NPY_DATETIMEUNIT, default NPY_FR_ns
  322. Returns
  323. -------
  324. obj : _TSObject
  325. """
  326. cdef:
  327. _TSObject obj = _TSObject()
  328. int64_t pps
  329. obj.creso = reso
  330. obj.fold = ts.fold
  331. if tz is not None:
  332. if ts.tzinfo is not None:
  333. # Convert the current timezone to the passed timezone
  334. ts = ts.astimezone(tz)
  335. pydatetime_to_dtstruct(ts, &obj.dts)
  336. obj.tzinfo = ts.tzinfo
  337. elif not is_utc(tz):
  338. ts = _localize_pydatetime(ts, tz)
  339. pydatetime_to_dtstruct(ts, &obj.dts)
  340. obj.tzinfo = ts.tzinfo
  341. else:
  342. # UTC
  343. pydatetime_to_dtstruct(ts, &obj.dts)
  344. obj.tzinfo = tz
  345. else:
  346. pydatetime_to_dtstruct(ts, &obj.dts)
  347. obj.tzinfo = ts.tzinfo
  348. if isinstance(ts, ABCTimestamp):
  349. obj.dts.ps = ts.nanosecond * 1000
  350. if nanos:
  351. obj.dts.ps = nanos * 1000
  352. obj.value = npy_datetimestruct_to_datetime(reso, &obj.dts)
  353. if obj.tzinfo is not None and not is_utc(obj.tzinfo):
  354. offset = get_utcoffset(obj.tzinfo, ts)
  355. pps = periods_per_second(reso)
  356. obj.value -= int(offset.total_seconds() * pps)
  357. check_dts_bounds(&obj.dts, reso)
  358. check_overflows(obj, reso)
  359. return obj
  360. cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts,
  361. int tzoffset, tzinfo tz=None,
  362. NPY_DATETIMEUNIT reso=NPY_FR_ns):
  363. """
  364. Convert a datetimestruct `dts`, along with initial timezone offset
  365. `tzoffset` to a _TSObject (with timezone object `tz` - optional).
  366. Parameters
  367. ----------
  368. dts : npy_datetimestruct
  369. tzoffset : int
  370. tz : tzinfo or None
  371. timezone for the timezone-aware output.
  372. reso : NPY_DATETIMEUNIT, default NPY_FR_ns
  373. Returns
  374. -------
  375. obj : _TSObject
  376. """
  377. cdef:
  378. _TSObject obj = _TSObject()
  379. int64_t value # numpy dt64
  380. datetime dt
  381. Py_ssize_t pos
  382. value = npy_datetimestruct_to_datetime(reso, &dts)
  383. obj.dts = dts
  384. obj.tzinfo = timezone(timedelta(minutes=tzoffset))
  385. obj.value = tz_localize_to_utc_single(
  386. value, obj.tzinfo, ambiguous=None, nonexistent=None, creso=reso
  387. )
  388. obj.creso = reso
  389. if tz is None:
  390. check_overflows(obj, reso)
  391. return obj
  392. cdef:
  393. Localizer info = Localizer(tz, reso)
  394. # Infer fold from offset-adjusted obj.value
  395. # see PEP 495 https://www.python.org/dev/peps/pep-0495/#the-fold-attribute
  396. if info.use_utc:
  397. pass
  398. elif info.use_tzlocal:
  399. info.utc_val_to_local_val(obj.value, &pos, &obj.fold)
  400. elif info.use_dst and not info.use_pytz:
  401. # i.e. dateutil
  402. info.utc_val_to_local_val(obj.value, &pos, &obj.fold)
  403. # Keep the converter same as PyDateTime's
  404. dt = datetime(obj.dts.year, obj.dts.month, obj.dts.day,
  405. obj.dts.hour, obj.dts.min, obj.dts.sec,
  406. obj.dts.us, obj.tzinfo, fold=obj.fold)
  407. obj = convert_datetime_to_tsobject(
  408. dt, tz, nanos=obj.dts.ps // 1000)
  409. obj.ensure_reso(reso) # TODO: more performant to get reso right up front?
  410. return obj
  411. cdef _TSObject convert_str_to_tsobject(str ts, tzinfo tz, str unit,
  412. bint dayfirst=False,
  413. bint yearfirst=False):
  414. """
  415. Convert a string input `ts`, along with optional timezone object`tz`
  416. to a _TSObject.
  417. The optional arguments `dayfirst` and `yearfirst` are passed to the
  418. dateutil parser.
  419. Parameters
  420. ----------
  421. ts : str
  422. Value to be converted to _TSObject
  423. tz : tzinfo or None
  424. timezone for the timezone-aware output
  425. unit : str or None
  426. dayfirst : bool, default False
  427. When parsing an ambiguous date string, interpret e.g. "3/4/1975" as
  428. April 3, as opposed to the standard US interpretation March 4.
  429. yearfirst : bool, default False
  430. When parsing an ambiguous date string, interpret e.g. "01/05/09"
  431. as "May 9, 2001", as opposed to the default "Jan 5, 2009"
  432. Returns
  433. -------
  434. obj : _TSObject
  435. """
  436. cdef:
  437. npy_datetimestruct dts
  438. int out_local = 0, out_tzoffset = 0, string_to_dts_failed
  439. datetime dt
  440. int64_t ival
  441. NPY_DATETIMEUNIT out_bestunit, reso
  442. if len(ts) == 0 or ts in nat_strings:
  443. obj = _TSObject()
  444. obj.value = NPY_NAT
  445. obj.tzinfo = tz
  446. return obj
  447. elif ts == "now":
  448. # Issue 9000, we short-circuit rather than going
  449. # into np_datetime_strings which returns utc
  450. dt = datetime.now(tz)
  451. elif ts == "today":
  452. # Issue 9000, we short-circuit rather than going
  453. # into np_datetime_strings which returns a normalized datetime
  454. dt = datetime.now(tz)
  455. # equiv: datetime.today().replace(tzinfo=tz)
  456. else:
  457. string_to_dts_failed = string_to_dts(
  458. ts, &dts, &out_bestunit, &out_local,
  459. &out_tzoffset, False
  460. )
  461. if not string_to_dts_failed:
  462. reso = get_supported_reso(out_bestunit)
  463. check_dts_bounds(&dts, reso)
  464. if out_local == 1:
  465. return _create_tsobject_tz_using_offset(
  466. dts, out_tzoffset, tz, reso
  467. )
  468. else:
  469. ival = npy_datetimestruct_to_datetime(reso, &dts)
  470. if tz is not None:
  471. # shift for _localize_tso
  472. ival = tz_localize_to_utc_single(
  473. ival, tz, ambiguous="raise", nonexistent=None, creso=reso
  474. )
  475. obj = _TSObject()
  476. obj.dts = dts
  477. obj.value = ival
  478. obj.creso = reso
  479. maybe_localize_tso(obj, tz, obj.creso)
  480. return obj
  481. dt = parse_datetime_string(
  482. ts, dayfirst=dayfirst, yearfirst=yearfirst, out_bestunit=&out_bestunit
  483. )
  484. reso = get_supported_reso(out_bestunit)
  485. return convert_datetime_to_tsobject(dt, tz, nanos=0, reso=reso)
  486. return convert_datetime_to_tsobject(dt, tz)
  487. cdef check_overflows(_TSObject obj, NPY_DATETIMEUNIT reso=NPY_FR_ns):
  488. """
  489. Check that we haven't silently overflowed in timezone conversion
  490. Parameters
  491. ----------
  492. obj : _TSObject
  493. reso : NPY_DATETIMEUNIT, default NPY_FR_ns
  494. Returns
  495. -------
  496. None
  497. Raises
  498. ------
  499. OutOfBoundsDatetime
  500. """
  501. # GH#12677
  502. cdef:
  503. npy_datetimestruct lb, ub
  504. get_implementation_bounds(reso, &lb, &ub)
  505. if obj.dts.year == lb.year:
  506. if not (obj.value < 0):
  507. from pandas._libs.tslibs.timestamps import Timestamp
  508. fmt = (f"{obj.dts.year}-{obj.dts.month:02d}-{obj.dts.day:02d} "
  509. f"{obj.dts.hour:02d}:{obj.dts.min:02d}:{obj.dts.sec:02d}")
  510. raise OutOfBoundsDatetime(
  511. f"Converting {fmt} underflows past {Timestamp.min}"
  512. )
  513. elif obj.dts.year == ub.year:
  514. if not (obj.value > 0):
  515. from pandas._libs.tslibs.timestamps import Timestamp
  516. fmt = (f"{obj.dts.year}-{obj.dts.month:02d}-{obj.dts.day:02d} "
  517. f"{obj.dts.hour:02d}:{obj.dts.min:02d}:{obj.dts.sec:02d}")
  518. raise OutOfBoundsDatetime(
  519. f"Converting {fmt} overflows past {Timestamp.max}"
  520. )
  521. # ----------------------------------------------------------------------
  522. # Localization
  523. cdef void _localize_tso(_TSObject obj, tzinfo tz, NPY_DATETIMEUNIT reso):
  524. """
  525. Given the UTC nanosecond timestamp in obj.value, find the wall-clock
  526. representation of that timestamp in the given timezone.
  527. Parameters
  528. ----------
  529. obj : _TSObject
  530. tz : tzinfo
  531. reso : NPY_DATETIMEUNIT
  532. Returns
  533. -------
  534. None
  535. Notes
  536. -----
  537. Sets obj.tzinfo inplace, alters obj.dts inplace.
  538. """
  539. cdef:
  540. int64_t local_val
  541. Py_ssize_t outpos = -1
  542. Localizer info = Localizer(tz, reso)
  543. assert obj.tzinfo is None
  544. if info.use_utc:
  545. pass
  546. elif obj.value == NPY_NAT:
  547. pass
  548. else:
  549. local_val = info.utc_val_to_local_val(obj.value, &outpos, &obj.fold)
  550. if info.use_pytz:
  551. # infer we went through a pytz path, will have outpos!=-1
  552. tz = tz._tzinfos[tz._transition_info[outpos]]
  553. pandas_datetime_to_datetimestruct(local_val, reso, &obj.dts)
  554. obj.tzinfo = tz
  555. cdef datetime _localize_pydatetime(datetime dt, tzinfo tz):
  556. """
  557. Take a datetime/Timestamp in UTC and localizes to timezone tz.
  558. NB: Unlike the public version, this treats datetime and Timestamp objects
  559. identically, i.e. discards nanos from Timestamps.
  560. It also assumes that the `tz` input is not None.
  561. """
  562. try:
  563. # datetime.replace with pytz may be incorrect result
  564. return tz.localize(dt)
  565. except AttributeError:
  566. return dt.replace(tzinfo=tz)
  567. cpdef inline datetime localize_pydatetime(datetime dt, tzinfo tz):
  568. """
  569. Take a datetime/Timestamp in UTC and localizes to timezone tz.
  570. Parameters
  571. ----------
  572. dt : datetime or Timestamp
  573. tz : tzinfo or None
  574. Returns
  575. -------
  576. localized : datetime or Timestamp
  577. """
  578. if tz is None:
  579. return dt
  580. elif isinstance(dt, ABCTimestamp):
  581. return dt.tz_localize(tz)
  582. return _localize_pydatetime(dt, tz)
  583. cdef tzinfo convert_timezone(
  584. tzinfo tz_in,
  585. tzinfo tz_out,
  586. bint found_naive,
  587. bint found_tz,
  588. bint utc_convert,
  589. ):
  590. """
  591. Validate that ``tz_in`` can be converted/localized to ``tz_out``.
  592. Parameters
  593. ----------
  594. tz_in : tzinfo or None
  595. Timezone info of element being processed.
  596. tz_out : tzinfo or None
  597. Timezone info of output.
  598. found_naive : bool
  599. Whether a timezone-naive element has been found so far.
  600. found_tz : bool
  601. Whether a timezone-aware element has been found so far.
  602. utc_convert : bool
  603. Whether to convert/localize to UTC.
  604. Returns
  605. -------
  606. tz_info
  607. Timezone info of output.
  608. Raises
  609. ------
  610. ValueError
  611. If ``tz_in`` can't be converted/localized to ``tz_out``.
  612. """
  613. if tz_in is not None:
  614. if utc_convert:
  615. pass
  616. elif found_naive:
  617. raise ValueError("Tz-aware datetime.datetime "
  618. "cannot be converted to "
  619. "datetime64 unless utc=True")
  620. elif tz_out is not None and not tz_compare(tz_out, tz_in):
  621. raise ValueError("Tz-aware datetime.datetime "
  622. "cannot be converted to "
  623. "datetime64 unless utc=True")
  624. else:
  625. tz_out = tz_in
  626. else:
  627. if found_tz and not utc_convert:
  628. raise ValueError("Cannot mix tz-aware with "
  629. "tz-naive values")
  630. return tz_out
  631. cdef int64_t parse_pydatetime(
  632. datetime val,
  633. npy_datetimestruct *dts,
  634. bint utc_convert,
  635. ) except? -1:
  636. """
  637. Convert pydatetime to datetime64.
  638. Parameters
  639. ----------
  640. val : datetime
  641. Element being processed.
  642. dts : *npy_datetimestruct
  643. Needed to use in pydatetime_to_dt64, which writes to it.
  644. utc_convert : bool
  645. Whether to convert/localize to UTC.
  646. Raises
  647. ------
  648. OutOfBoundsDatetime
  649. """
  650. cdef:
  651. _TSObject _ts
  652. int64_t result
  653. if val.tzinfo is not None:
  654. if utc_convert:
  655. _ts = convert_datetime_to_tsobject(val, None)
  656. _ts.ensure_reso(NPY_FR_ns)
  657. result = _ts.value
  658. else:
  659. _ts = convert_datetime_to_tsobject(val, None)
  660. _ts.ensure_reso(NPY_FR_ns)
  661. result = _ts.value
  662. else:
  663. if isinstance(val, _Timestamp):
  664. result = val.as_unit("ns")._value
  665. else:
  666. result = pydatetime_to_dt64(val, dts)
  667. check_dts_bounds(dts)
  668. return result