Skip to content

Commit f4f6529

Browse files
jbrockmendelyehoshuadimarsky
authored andcommitted
ENH: Timestamp.month_name, day_name support non-nano (pandas-dev#46959)
* ENH: Timestamp.month_name, day_name support non-nano * restore comment
1 parent 5c70d42 commit f4f6529

File tree

7 files changed

+52
-29
lines changed

7 files changed

+52
-29
lines changed

asv_bench/benchmarks/tslibs/fields.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ class TimeGetStartEndField:
6666

6767
def setup(self, size, side, period, freqstr, month_kw):
6868
arr = np.random.randint(0, 10, size=size, dtype="i8")
69-
self.dt64data = arr.view("M8[ns]")
69+
self.i8data = arr
7070

7171
self.attrname = f"is_{period}_{side}"
7272

7373
def time_get_start_end_field(self, size, side, period, freqstr, month_kw):
74-
get_start_end_field(self.dt64data, self.attrname, freqstr, month_kw=month_kw)
74+
get_start_end_field(self.i8data, self.attrname, freqstr, month_kw=month_kw)

pandas/_libs/tslibs/fields.pyi

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ def get_date_name_field(
1010
dtindex: npt.NDArray[np.int64], # const int64_t[:]
1111
field: str,
1212
locale: str | None = ...,
13+
reso: int = ..., # NPY_DATETIMEUNIT
1314
) -> npt.NDArray[np.object_]: ...
1415
def get_start_end_field(
15-
dt64values: npt.NDArray[np.datetime64],
16+
dtindex: npt.NDArray[np.int64],
1617
field: str,
1718
freqstr: str | None = ...,
1819
month_kw: int = ...,
20+
reso: int = ..., # NPY_DATETIMEUNIT
1921
) -> npt.NDArray[np.bool_]: ...
2022
def get_date_field(
2123
dtindex: npt.NDArray[np.int64], # const int64_t[:]

pandas/_libs/tslibs/fields.pyx

+20-10
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ from pandas._libs.tslibs.ccalendar cimport (
4444
from pandas._libs.tslibs.nattype cimport NPY_NAT
4545
from pandas._libs.tslibs.np_datetime cimport (
4646
NPY_DATETIMEUNIT,
47+
NPY_FR_ns,
4748
dt64_to_dtstruct,
4849
get_unit_from_dtype,
4950
npy_datetimestruct,
@@ -139,13 +140,18 @@ def month_position_check(fields, weekdays) -> str | None:
139140

140141
@cython.wraparound(False)
141142
@cython.boundscheck(False)
142-
def get_date_name_field(const int64_t[:] dtindex, str field, object locale=None):
143+
def get_date_name_field(
144+
const int64_t[:] dtindex,
145+
str field,
146+
object locale=None,
147+
NPY_DATETIMEUNIT reso=NPY_FR_ns,
148+
):
143149
"""
144150
Given a int64-based datetime index, return array of strings of date
145151
name based on requested field (e.g. day_name)
146152
"""
147153
cdef:
148-
Py_ssize_t i, count = len(dtindex)
154+
Py_ssize_t i, count = dtindex.shape[0]
149155
ndarray[object] out, names
150156
npy_datetimestruct dts
151157
int dow
@@ -163,7 +169,7 @@ def get_date_name_field(const int64_t[:] dtindex, str field, object locale=None)
163169
out[i] = np.nan
164170
continue
165171

166-
dt64_to_dtstruct(dtindex[i], &dts)
172+
pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
167173
dow = dayofweek(dts.year, dts.month, dts.day)
168174
out[i] = names[dow].capitalize()
169175

@@ -178,7 +184,7 @@ def get_date_name_field(const int64_t[:] dtindex, str field, object locale=None)
178184
out[i] = np.nan
179185
continue
180186

181-
dt64_to_dtstruct(dtindex[i], &dts)
187+
pandas_datetime_to_datetimestruct(dtindex[i], reso, &dts)
182188
out[i] = names[dts.month].capitalize()
183189

184190
else:
@@ -201,35 +207,39 @@ cdef inline bint _is_on_month(int month, int compare_month, int modby) nogil:
201207

202208
@cython.wraparound(False)
203209
@cython.boundscheck(False)
204-
def get_start_end_field(ndarray dt64values, str field,
205-
str freqstr=None, int month_kw=12):
210+
def get_start_end_field(
211+
const int64_t[:] dtindex,
212+
str field,
213+
str freqstr=None,
214+
int month_kw=12,
215+
NPY_DATETIMEUNIT reso=NPY_FR_ns,
216+
):
206217
"""
207218
Given an int64-based datetime index return array of indicators
208219
of whether timestamps are at the start/end of the month/quarter/year
209220
(defined by frequency).
210221
211222
Parameters
212223
----------
213-
dt64values : ndarray[datetime64], any resolution
224+
dtindex : ndarray[int64]
214225
field : str
215226
frestr : str or None, default None
216227
month_kw : int, default 12
228+
reso : NPY_DATETIMEUNIT, default NPY_FR_ns
217229
218230
Returns
219231
-------
220232
ndarray[bool]
221233
"""
222234
cdef:
223235
Py_ssize_t i
224-
int count = dt64values.size
236+
int count = dtindex.shape[0]
225237
bint is_business = 0
226238
int end_month = 12
227239
int start_month = 1
228240
ndarray[int8_t] out
229241
npy_datetimestruct dts
230242
int compare_month, modby
231-
ndarray dtindex = dt64values.view("i8")
232-
NPY_DATETIMEUNIT reso = get_unit_from_dtype(dt64values.dtype)
233243

234244
out = np.zeros(count, dtype='int8')
235245

pandas/_libs/tslibs/timestamps.pyx

+4-8
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,6 @@ cdef class _Timestamp(ABCTimestamp):
487487
dict kwds
488488
ndarray[uint8_t, cast=True] out
489489
int month_kw
490-
str unit
491490

492491
if freq:
493492
kwds = freq.kwds
@@ -499,9 +498,8 @@ cdef class _Timestamp(ABCTimestamp):
499498

500499
val = self._maybe_convert_value_to_local()
501500

502-
unit = npy_unit_to_abbrev(self._reso)
503-
out = get_start_end_field(np.array([val], dtype=f"M8[{unit}]"),
504-
field, freqstr, month_kw)
501+
out = get_start_end_field(np.array([val], dtype=np.int64),
502+
field, freqstr, month_kw, self._reso)
505503
return out[0]
506504

507505
cdef _warn_on_field_deprecation(self, freq, str field):
@@ -661,12 +659,10 @@ cdef class _Timestamp(ABCTimestamp):
661659
int64_t val
662660
object[::1] out
663661

664-
if self._reso != NPY_FR_ns:
665-
raise NotImplementedError(self._reso)
666-
667662
val = self._maybe_convert_value_to_local()
663+
668664
out = get_date_name_field(np.array([val], dtype=np.int64),
669-
field, locale=locale)
665+
field, locale=locale, reso=self._reso)
670666
return out[0]
671667

672668
def day_name(self, locale=None) -> str:

pandas/core/arrays/datetimes.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@
3838
tz_convert_from_utc,
3939
tzconversion,
4040
)
41+
from pandas._libs.tslibs.np_datetime import py_get_unit_from_dtype
4142
from pandas._typing import npt
4243
from pandas.errors import (
4344
OutOfBoundsDatetime,
4445
PerformanceWarning,
4546
)
47+
from pandas.util._decorators import cache_readonly
4648
from pandas.util._exceptions import find_stack_level
4749
from pandas.util._validators import validate_inclusive
4850

@@ -131,7 +133,7 @@ def f(self):
131133
month_kw = kwds.get("startingMonth", kwds.get("month", 12))
132134

133135
result = fields.get_start_end_field(
134-
values.view(self._ndarray.dtype), field, self.freqstr, month_kw
136+
values, field, self.freqstr, month_kw, reso=self._reso
135137
)
136138
else:
137139
result = fields.get_date_field(values, field)
@@ -140,7 +142,7 @@ def f(self):
140142
return result
141143

142144
if field in self._object_ops:
143-
result = fields.get_date_name_field(values, field)
145+
result = fields.get_date_name_field(values, field, reso=self._reso)
144146
result = self._maybe_mask_results(result, fill_value=None)
145147

146148
else:
@@ -544,6 +546,10 @@ def _check_compatible_with(self, other, setitem: bool = False):
544546
# -----------------------------------------------------------------
545547
# Descriptive Properties
546548

549+
@cache_readonly
550+
def _reso(self):
551+
return py_get_unit_from_dtype(self._ndarray.dtype)
552+
547553
def _box_func(self, x: np.datetime64) -> Timestamp | NaTType:
548554
# GH#42228
549555
value = x.view("i8")
@@ -1270,7 +1276,9 @@ def month_name(self, locale=None):
12701276
"""
12711277
values = self._local_timestamps()
12721278

1273-
result = fields.get_date_name_field(values, "month_name", locale=locale)
1279+
result = fields.get_date_name_field(
1280+
values, "month_name", locale=locale, reso=self._reso
1281+
)
12741282
result = self._maybe_mask_results(result, fill_value=None)
12751283
return result
12761284

@@ -1313,7 +1321,9 @@ def day_name(self, locale=None):
13131321
"""
13141322
values = self._local_timestamps()
13151323

1316-
result = fields.get_date_name_field(values, "day_name", locale=locale)
1324+
result = fields.get_date_name_field(
1325+
values, "day_name", locale=locale, reso=self._reso
1326+
)
13171327
result = self._maybe_mask_results(result, fill_value=None)
13181328
return result
13191329

pandas/tests/scalar/timestamp/test_timestamp.py

+8
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,14 @@ def test_start_end_fields(self, ts):
753753
assert not ts.is_month_end
754754
assert not ts.is_month_end
755755

756+
def test_day_name(self, dt64, ts):
757+
alt = Timestamp(dt64)
758+
assert ts.day_name() == alt.day_name()
759+
760+
def test_month_name(self, dt64, ts):
761+
alt = Timestamp(dt64)
762+
assert ts.month_name() == alt.month_name()
763+
756764
def test_repr(self, dt64, ts):
757765
alt = Timestamp(dt64)
758766

pandas/tests/tslibs/test_fields.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ def test_get_date_field_readonly(dtindex):
2828

2929

3030
def test_get_start_end_field_readonly(dtindex):
31-
dt64values = dtindex.view("M8[ns]")
32-
dt64values.flags.writeable = False
33-
34-
result = fields.get_start_end_field(dt64values, "is_month_start", None)
31+
result = fields.get_start_end_field(dtindex, "is_month_start", None)
3532
expected = np.array([True, False, False, False, False], dtype=np.bool_)
3633
tm.assert_numpy_array_equal(result, expected)
3734

0 commit comments

Comments
 (0)