fields.pyx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. """
  2. Functions for accessing attributes of Timestamp/datetime64/datetime-like
  3. objects and arrays
  4. """
  5. from locale import LC_TIME
  6. from _strptime import LocaleTime
  7. cimport cython
  8. from cython cimport Py_ssize_t
  9. import numpy as np
  10. cimport numpy as cnp
  11. from numpy cimport (
  12. int8_t,
  13. int32_t,
  14. int64_t,
  15. ndarray,
  16. uint32_t,
  17. )
  18. cnp.import_array()
  19. from pandas._config.localization import set_locale
  20. from pandas._libs.tslibs.ccalendar import (
  21. DAYS_FULL,
  22. MONTHS_FULL,
  23. )
  24. from pandas._libs.tslibs.ccalendar cimport (
  25. dayofweek,
  26. get_day_of_year,
  27. get_days_in_month,
  28. get_firstbday,
  29. get_iso_calendar,
  30. get_lastbday,
  31. get_week_of_year,
  32. iso_calendar_t,
  33. )
  34. from pandas._libs.tslibs.nattype cimport NPY_NAT
  35. from pandas._libs.tslibs.np_datetime cimport (
  36. NPY_DATETIMEUNIT,
  37. NPY_FR_ns,
  38. npy_datetimestruct,
  39. pandas_datetime_to_datetimestruct,
  40. pandas_timedelta_to_timedeltastruct,
  41. pandas_timedeltastruct,
  42. )
  43. @cython.wraparound(False)
  44. @cython.boundscheck(False)
  45. def build_field_sarray(const int64_t[:] dtindex, NPY_DATETIMEUNIT reso):
  46. """
  47. Datetime as int64 representation to a structured array of fields
  48. """
  49. cdef:
  50. Py_ssize_t i, count = len(dtindex)
  51. npy_datetimestruct dts
  52. ndarray[int32_t] years, months, days, hours, minutes, seconds, mus
  53. sa_dtype = [
  54. ("Y", "i4"), # year
  55. ("M", "i4"), # month
  56. ("D", "i4"), # day
  57. ("h", "i4"), # hour
  58. ("m", "i4"), # min
  59. ("s", "i4"), # second
  60. ("u", "i4"), # microsecond
  61. ]
  62. out = np.empty(count, dtype=sa_dtype)
  63. years = out["Y"]
  64. months = out["M"]
  65. days = out["D"]
  66. hours = out["h"]
  67. minutes = out["m"]
  68. seconds = out["s"]
  69. mus = out["u"]
  70. for i in range(count):
  71. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  72. years[i] = dts.year
  73. months[i] = dts.month
  74. days[i] = dts.day
  75. hours[i] = dts.hour
  76. minutes[i] = dts.min
  77. seconds[i] = dts.sec
  78. mus[i] = dts.us
  79. return out
  80. def month_position_check(fields, weekdays) -> str | None:
  81. cdef:
  82. int32_t daysinmonth, y, m, d
  83. bint calendar_end = True
  84. bint business_end = True
  85. bint calendar_start = True
  86. bint business_start = True
  87. bint cal
  88. int32_t[:] years = fields["Y"]
  89. int32_t[:] months = fields["M"]
  90. int32_t[:] days = fields["D"]
  91. for y, m, d, wd in zip(years, months, days, weekdays):
  92. if calendar_start:
  93. calendar_start &= d == 1
  94. if business_start:
  95. business_start &= d == 1 or (d <= 3 and wd == 0)
  96. if calendar_end or business_end:
  97. daysinmonth = get_days_in_month(y, m)
  98. cal = d == daysinmonth
  99. if calendar_end:
  100. calendar_end &= cal
  101. if business_end:
  102. business_end &= cal or (daysinmonth - d < 3 and wd == 4)
  103. elif not calendar_start and not business_start:
  104. break
  105. if calendar_end:
  106. return "ce"
  107. elif business_end:
  108. return "be"
  109. elif calendar_start:
  110. return "cs"
  111. elif business_start:
  112. return "bs"
  113. else:
  114. return None
  115. @cython.wraparound(False)
  116. @cython.boundscheck(False)
  117. def get_date_name_field(
  118. const int64_t[:] dtindex,
  119. str field,
  120. object locale=None,
  121. NPY_DATETIMEUNIT reso=NPY_FR_ns,
  122. ):
  123. """
  124. Given a int64-based datetime index, return array of strings of date
  125. name based on requested field (e.g. day_name)
  126. """
  127. cdef:
  128. Py_ssize_t i, count = dtindex.shape[0]
  129. ndarray[object] out, names
  130. npy_datetimestruct dts
  131. int dow
  132. out = np.empty(count, dtype=object)
  133. if field == "day_name":
  134. if locale is None:
  135. names = np.array(DAYS_FULL, dtype=np.object_)
  136. else:
  137. names = np.array(_get_locale_names("f_weekday", locale),
  138. dtype=np.object_)
  139. for i in range(count):
  140. if dtindex[i] == NPY_NAT:
  141. out[i] = np.nan
  142. continue
  143. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  144. dow = dayofweek(dts.year, dts.month, dts.day)
  145. out[i] = names[dow].capitalize()
  146. elif field == "month_name":
  147. if locale is None:
  148. names = np.array(MONTHS_FULL, dtype=np.object_)
  149. else:
  150. names = np.array(_get_locale_names("f_month", locale),
  151. dtype=np.object_)
  152. for i in range(count):
  153. if dtindex[i] == NPY_NAT:
  154. out[i] = np.nan
  155. continue
  156. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  157. out[i] = names[dts.month].capitalize()
  158. else:
  159. raise ValueError(f"Field {field} not supported")
  160. return out
  161. cdef bint _is_on_month(int month, int compare_month, int modby) nogil:
  162. """
  163. Analogous to DateOffset.is_on_offset checking for the month part of a date.
  164. """
  165. if modby == 1:
  166. return True
  167. elif modby == 3:
  168. return (month - compare_month) % 3 == 0
  169. else:
  170. return month == compare_month
  171. @cython.wraparound(False)
  172. @cython.boundscheck(False)
  173. def get_start_end_field(
  174. const int64_t[:] dtindex,
  175. str field,
  176. str freqstr=None,
  177. int month_kw=12,
  178. NPY_DATETIMEUNIT reso=NPY_FR_ns,
  179. ):
  180. """
  181. Given an int64-based datetime index return array of indicators
  182. of whether timestamps are at the start/end of the month/quarter/year
  183. (defined by frequency).
  184. Parameters
  185. ----------
  186. dtindex : ndarray[int64]
  187. field : str
  188. frestr : str or None, default None
  189. month_kw : int, default 12
  190. reso : NPY_DATETIMEUNIT, default NPY_FR_ns
  191. Returns
  192. -------
  193. ndarray[bool]
  194. """
  195. cdef:
  196. Py_ssize_t i
  197. int count = dtindex.shape[0]
  198. bint is_business = 0
  199. int end_month = 12
  200. int start_month = 1
  201. ndarray[int8_t] out
  202. npy_datetimestruct dts
  203. int compare_month, modby
  204. out = np.zeros(count, dtype="int8")
  205. if freqstr:
  206. if freqstr == "C":
  207. raise ValueError(f"Custom business days is not supported by {field}")
  208. is_business = freqstr[0] == "B"
  209. # YearBegin(), BYearBegin() use month = starting month of year.
  210. # QuarterBegin(), BQuarterBegin() use startingMonth = starting
  211. # month of year. Other offsets use month, startingMonth as ending
  212. # month of year.
  213. if (freqstr[0:2] in ["MS", "QS", "AS"]) or (
  214. freqstr[1:3] in ["MS", "QS", "AS"]):
  215. end_month = 12 if month_kw == 1 else month_kw - 1
  216. start_month = month_kw
  217. else:
  218. end_month = month_kw
  219. start_month = (end_month % 12) + 1
  220. else:
  221. end_month = 12
  222. start_month = 1
  223. compare_month = start_month if "start" in field else end_month
  224. if "month" in field:
  225. modby = 1
  226. elif "quarter" in field:
  227. modby = 3
  228. else:
  229. modby = 12
  230. if field in ["is_month_start", "is_quarter_start", "is_year_start"]:
  231. if is_business:
  232. for i in range(count):
  233. if dtindex[i] == NPY_NAT:
  234. out[i] = 0
  235. continue
  236. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  237. if _is_on_month(dts.month, compare_month, modby) and (
  238. dts.day == get_firstbday(dts.year, dts.month)):
  239. out[i] = 1
  240. else:
  241. for i in range(count):
  242. if dtindex[i] == NPY_NAT:
  243. out[i] = 0
  244. continue
  245. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  246. if _is_on_month(dts.month, compare_month, modby) and dts.day == 1:
  247. out[i] = 1
  248. elif field in ["is_month_end", "is_quarter_end", "is_year_end"]:
  249. if is_business:
  250. for i in range(count):
  251. if dtindex[i] == NPY_NAT:
  252. out[i] = 0
  253. continue
  254. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  255. if _is_on_month(dts.month, compare_month, modby) and (
  256. dts.day == get_lastbday(dts.year, dts.month)):
  257. out[i] = 1
  258. else:
  259. for i in range(count):
  260. if dtindex[i] == NPY_NAT:
  261. out[i] = 0
  262. continue
  263. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  264. if _is_on_month(dts.month, compare_month, modby) and (
  265. dts.day == get_days_in_month(dts.year, dts.month)):
  266. out[i] = 1
  267. else:
  268. raise ValueError(f"Field {field} not supported")
  269. return out.view(bool)
  270. @cython.wraparound(False)
  271. @cython.boundscheck(False)
  272. def get_date_field(
  273. const int64_t[:] dtindex,
  274. str field,
  275. NPY_DATETIMEUNIT reso=NPY_FR_ns,
  276. ):
  277. """
  278. Given a int64-based datetime index, extract the year, month, etc.,
  279. field and return an array of these values.
  280. """
  281. cdef:
  282. Py_ssize_t i, count = len(dtindex)
  283. ndarray[int32_t] out
  284. npy_datetimestruct dts
  285. out = np.empty(count, dtype="i4")
  286. if field == "Y":
  287. with nogil:
  288. for i in range(count):
  289. if dtindex[i] == NPY_NAT:
  290. out[i] = -1
  291. continue
  292. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  293. out[i] = dts.year
  294. return out
  295. elif field == "M":
  296. with nogil:
  297. for i in range(count):
  298. if dtindex[i] == NPY_NAT:
  299. out[i] = -1
  300. continue
  301. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  302. out[i] = dts.month
  303. return out
  304. elif field == "D":
  305. with nogil:
  306. for i in range(count):
  307. if dtindex[i] == NPY_NAT:
  308. out[i] = -1
  309. continue
  310. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  311. out[i] = dts.day
  312. return out
  313. elif field == "h":
  314. with nogil:
  315. for i in range(count):
  316. if dtindex[i] == NPY_NAT:
  317. out[i] = -1
  318. continue
  319. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  320. out[i] = dts.hour
  321. # TODO: can we de-dup with period.pyx <accessor>s?
  322. return out
  323. elif field == "m":
  324. with nogil:
  325. for i in range(count):
  326. if dtindex[i] == NPY_NAT:
  327. out[i] = -1
  328. continue
  329. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  330. out[i] = dts.min
  331. return out
  332. elif field == "s":
  333. with nogil:
  334. for i in range(count):
  335. if dtindex[i] == NPY_NAT:
  336. out[i] = -1
  337. continue
  338. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  339. out[i] = dts.sec
  340. return out
  341. elif field == "us":
  342. with nogil:
  343. for i in range(count):
  344. if dtindex[i] == NPY_NAT:
  345. out[i] = -1
  346. continue
  347. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  348. out[i] = dts.us
  349. return out
  350. elif field == "ns":
  351. with nogil:
  352. for i in range(count):
  353. if dtindex[i] == NPY_NAT:
  354. out[i] = -1
  355. continue
  356. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  357. out[i] = dts.ps // 1000
  358. return out
  359. elif field == "doy":
  360. with nogil:
  361. for i in range(count):
  362. if dtindex[i] == NPY_NAT:
  363. out[i] = -1
  364. continue
  365. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  366. out[i] = get_day_of_year(dts.year, dts.month, dts.day)
  367. return out
  368. elif field == "dow":
  369. with nogil:
  370. for i in range(count):
  371. if dtindex[i] == NPY_NAT:
  372. out[i] = -1
  373. continue
  374. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  375. out[i] = dayofweek(dts.year, dts.month, dts.day)
  376. return out
  377. elif field == "woy":
  378. with nogil:
  379. for i in range(count):
  380. if dtindex[i] == NPY_NAT:
  381. out[i] = -1
  382. continue
  383. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  384. out[i] = get_week_of_year(dts.year, dts.month, dts.day)
  385. return out
  386. elif field == "q":
  387. with nogil:
  388. for i in range(count):
  389. if dtindex[i] == NPY_NAT:
  390. out[i] = -1
  391. continue
  392. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  393. out[i] = dts.month
  394. out[i] = ((out[i] - 1) // 3) + 1
  395. return out
  396. elif field == "dim":
  397. with nogil:
  398. for i in range(count):
  399. if dtindex[i] == NPY_NAT:
  400. out[i] = -1
  401. continue
  402. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  403. out[i] = get_days_in_month(dts.year, dts.month)
  404. return out
  405. elif field == "is_leap_year":
  406. return isleapyear_arr(get_date_field(dtindex, "Y", reso=reso))
  407. raise ValueError(f"Field {field} not supported")
  408. @cython.wraparound(False)
  409. @cython.boundscheck(False)
  410. def get_timedelta_field(
  411. const int64_t[:] tdindex,
  412. str field,
  413. NPY_DATETIMEUNIT reso=NPY_FR_ns,
  414. ):
  415. """
  416. Given a int64-based timedelta index, extract the days, hrs, sec.,
  417. field and return an array of these values.
  418. """
  419. cdef:
  420. Py_ssize_t i, count = len(tdindex)
  421. ndarray[int32_t] out
  422. pandas_timedeltastruct tds
  423. out = np.empty(count, dtype="i4")
  424. if field == "seconds":
  425. with nogil:
  426. for i in range(count):
  427. if tdindex[i] == NPY_NAT:
  428. out[i] = -1
  429. continue
  430. pandas_timedelta_to_timedeltastruct(tdindex[i], reso, &tds)
  431. out[i] = tds.seconds
  432. return out
  433. elif field == "microseconds":
  434. with nogil:
  435. for i in range(count):
  436. if tdindex[i] == NPY_NAT:
  437. out[i] = -1
  438. continue
  439. pandas_timedelta_to_timedeltastruct(tdindex[i], reso, &tds)
  440. out[i] = tds.microseconds
  441. return out
  442. elif field == "nanoseconds":
  443. with nogil:
  444. for i in range(count):
  445. if tdindex[i] == NPY_NAT:
  446. out[i] = -1
  447. continue
  448. pandas_timedelta_to_timedeltastruct(tdindex[i], reso, &tds)
  449. out[i] = tds.nanoseconds
  450. return out
  451. raise ValueError(f"Field {field} not supported")
  452. @cython.wraparound(False)
  453. @cython.boundscheck(False)
  454. def get_timedelta_days(
  455. const int64_t[:] tdindex,
  456. NPY_DATETIMEUNIT reso=NPY_FR_ns,
  457. ):
  458. """
  459. Given a int64-based timedelta index, extract the days,
  460. field and return an array of these values.
  461. """
  462. cdef:
  463. Py_ssize_t i, count = len(tdindex)
  464. ndarray[int64_t] out
  465. pandas_timedeltastruct tds
  466. out = np.empty(count, dtype="i8")
  467. with nogil:
  468. for i in range(count):
  469. if tdindex[i] == NPY_NAT:
  470. out[i] = -1
  471. continue
  472. pandas_timedelta_to_timedeltastruct(tdindex[i], reso, &tds)
  473. out[i] = tds.days
  474. return out
  475. cpdef isleapyear_arr(ndarray years):
  476. """vectorized version of isleapyear; NaT evaluates as False"""
  477. cdef:
  478. ndarray[int8_t] out
  479. out = np.zeros(len(years), dtype="int8")
  480. out[np.logical_or(years % 400 == 0,
  481. np.logical_and(years % 4 == 0,
  482. years % 100 > 0))] = 1
  483. return out.view(bool)
  484. @cython.wraparound(False)
  485. @cython.boundscheck(False)
  486. def build_isocalendar_sarray(const int64_t[:] dtindex, NPY_DATETIMEUNIT reso):
  487. """
  488. Given a int64-based datetime array, return the ISO 8601 year, week, and day
  489. as a structured array.
  490. """
  491. cdef:
  492. Py_ssize_t i, count = len(dtindex)
  493. npy_datetimestruct dts
  494. ndarray[uint32_t] iso_years, iso_weeks, days
  495. iso_calendar_t ret_val
  496. sa_dtype = [
  497. ("year", "u4"),
  498. ("week", "u4"),
  499. ("day", "u4"),
  500. ]
  501. out = np.empty(count, dtype=sa_dtype)
  502. iso_years = out["year"]
  503. iso_weeks = out["week"]
  504. days = out["day"]
  505. with nogil:
  506. for i in range(count):
  507. if dtindex[i] == NPY_NAT:
  508. ret_val = 0, 0, 0
  509. else:
  510. pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
  511. ret_val = get_iso_calendar(dts.year, dts.month, dts.day)
  512. iso_years[i] = ret_val[0]
  513. iso_weeks[i] = ret_val[1]
  514. days[i] = ret_val[2]
  515. return out
  516. def _get_locale_names(name_type: str, locale: object = None):
  517. """
  518. Returns an array of localized day or month names.
  519. Parameters
  520. ----------
  521. name_type : str
  522. Attribute of LocaleTime() in which to return localized names.
  523. locale : str
  524. Returns
  525. -------
  526. list of locale names
  527. """
  528. with set_locale(locale, LC_TIME):
  529. return getattr(LocaleTime(), name_type)
  530. # ---------------------------------------------------------------------
  531. # Rounding
  532. class RoundTo:
  533. """
  534. enumeration defining the available rounding modes
  535. Attributes
  536. ----------
  537. MINUS_INFTY
  538. round towards -∞, or floor [2]_
  539. PLUS_INFTY
  540. round towards +∞, or ceil [3]_
  541. NEAREST_HALF_EVEN
  542. round to nearest, tie-break half to even [6]_
  543. NEAREST_HALF_MINUS_INFTY
  544. round to nearest, tie-break half to -∞ [5]_
  545. NEAREST_HALF_PLUS_INFTY
  546. round to nearest, tie-break half to +∞ [4]_
  547. References
  548. ----------
  549. .. [1] "Rounding - Wikipedia"
  550. https://en.wikipedia.org/wiki/Rounding
  551. .. [2] "Rounding down"
  552. https://en.wikipedia.org/wiki/Rounding#Rounding_down
  553. .. [3] "Rounding up"
  554. https://en.wikipedia.org/wiki/Rounding#Rounding_up
  555. .. [4] "Round half up"
  556. https://en.wikipedia.org/wiki/Rounding#Round_half_up
  557. .. [5] "Round half down"
  558. https://en.wikipedia.org/wiki/Rounding#Round_half_down
  559. .. [6] "Round half to even"
  560. https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
  561. """
  562. @property
  563. def MINUS_INFTY(self) -> int:
  564. return 0
  565. @property
  566. def PLUS_INFTY(self) -> int:
  567. return 1
  568. @property
  569. def NEAREST_HALF_EVEN(self) -> int:
  570. return 2
  571. @property
  572. def NEAREST_HALF_PLUS_INFTY(self) -> int:
  573. return 3
  574. @property
  575. def NEAREST_HALF_MINUS_INFTY(self) -> int:
  576. return 4
  577. cdef ndarray[int64_t] _floor_int64(const int64_t[:] values, int64_t unit):
  578. cdef:
  579. Py_ssize_t i, n = len(values)
  580. ndarray[int64_t] result = np.empty(n, dtype="i8")
  581. int64_t res, value
  582. with cython.overflowcheck(True):
  583. for i in range(n):
  584. value = values[i]
  585. if value == NPY_NAT:
  586. res = NPY_NAT
  587. else:
  588. res = value - value % unit
  589. result[i] = res
  590. return result
  591. cdef ndarray[int64_t] _ceil_int64(const int64_t[:] values, int64_t unit):
  592. cdef:
  593. Py_ssize_t i, n = len(values)
  594. ndarray[int64_t] result = np.empty(n, dtype="i8")
  595. int64_t res, value
  596. with cython.overflowcheck(True):
  597. for i in range(n):
  598. value = values[i]
  599. if value == NPY_NAT:
  600. res = NPY_NAT
  601. else:
  602. remainder = value % unit
  603. if remainder == 0:
  604. res = value
  605. else:
  606. res = value + (unit - remainder)
  607. result[i] = res
  608. return result
  609. cdef ndarray[int64_t] _rounddown_int64(values, int64_t unit):
  610. return _ceil_int64(values - unit // 2, unit)
  611. cdef ndarray[int64_t] _roundup_int64(values, int64_t unit):
  612. return _floor_int64(values + unit // 2, unit)
  613. def round_nsint64(values: np.ndarray, mode: RoundTo, nanos: int) -> np.ndarray:
  614. """
  615. Applies rounding mode at given frequency
  616. Parameters
  617. ----------
  618. values : np.ndarray[int64_t]`
  619. mode : instance of `RoundTo` enumeration
  620. nanos : np.int64
  621. Freq to round to, expressed in nanoseconds
  622. Returns
  623. -------
  624. np.ndarray[int64_t]
  625. """
  626. cdef:
  627. int64_t unit = nanos
  628. if mode == RoundTo.MINUS_INFTY:
  629. return _floor_int64(values, unit)
  630. elif mode == RoundTo.PLUS_INFTY:
  631. return _ceil_int64(values, unit)
  632. elif mode == RoundTo.NEAREST_HALF_MINUS_INFTY:
  633. return _rounddown_int64(values, unit)
  634. elif mode == RoundTo.NEAREST_HALF_PLUS_INFTY:
  635. return _roundup_int64(values, unit)
  636. elif mode == RoundTo.NEAREST_HALF_EVEN:
  637. # for odd unit there is no need of a tie break
  638. if unit % 2:
  639. return _rounddown_int64(values, unit)
  640. quotient, remainder = np.divmod(values, unit)
  641. mask = np.logical_or(
  642. remainder > (unit // 2),
  643. np.logical_and(remainder == (unit // 2), quotient % 2)
  644. )
  645. quotient[mask] += 1
  646. return quotient * unit
  647. # if/elif above should catch all rounding modes defined in enum 'RoundTo':
  648. # if flow of control arrives here, it is a bug
  649. raise ValueError("round_nsint64 called with an unrecognized rounding mode")