Skip to content

Commit 5f62767

Browse files
jbrockmendelphoflmroeschkejonashaag
authored
ENH: DTA to_pydatetime, time, timetz, date, iter support non-nano (#47307)
* ENH: Timestamp +- timedeltalike scalar support non-nano * ENH: Timestamp.__sub__(datetime) with non-nano * better exception message * BUG: concat not sorting mixed column names when None is included (#47331) * REGR: concat not sorting columns for mixed column names * Fix none in columns * BUG: concat not sorting column names when None is included * Update doc/source/whatsnew/v1.5.0.rst Co-authored-by: Matthew Roeschke <[email protected]> * Add gh reference Co-authored-by: Matthew Roeschke <[email protected]> * Add run-tests action (#47292) * Add run-tests action * Fix * Fix * Fix * Update macos-windows.yml * Update posix.yml * Update python-dev.yml * Update action.yml * Update macos-windows.yml * Update posix.yml * Update python-dev.yml * Update python-dev.yml * Update python-dev.yml * Update python-dev.yml * Update python-dev.yml * Update python-dev.yml * Update python-dev.yml * Update python-dev.yml * Update python-dev.yml * ENH: Timestamp pickle support non-nano tzaware (#47340) * ENH: DTA to_pydatetime, time, timetz, date, iter support non-nano * cast in liboffsets * mypy fixup Co-authored-by: Patrick Hoefler <[email protected]> Co-authored-by: Matthew Roeschke <[email protected]> Co-authored-by: Jonas Haag <[email protected]>
1 parent 6f0be79 commit 5f62767

File tree

6 files changed

+54
-14
lines changed

6 files changed

+54
-14
lines changed

pandas/_libs/tslibs/offsets.pyx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3115,7 +3115,7 @@ cdef class FY5253Quarter(FY5253Mixin):
31153115
for qlen in qtr_lens:
31163116
if qlen * 7 <= tdelta.days:
31173117
num_qtrs += 1
3118-
tdelta -= Timedelta(days=qlen * 7)
3118+
tdelta -= (<_Timedelta>Timedelta(days=qlen * 7))._as_reso(norm._reso)
31193119
else:
31203120
break
31213121
else:

pandas/_libs/tslibs/vectorized.pyi

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def ints_to_pydatetime(
3737
freq: BaseOffset | None = ...,
3838
fold: bool = ...,
3939
box: str = ...,
40+
reso: int = ..., # NPY_DATETIMEUNIT
4041
) -> npt.NDArray[np.object_]: ...
4142
def tz_convert_from_utc(
4243
stamps: npt.NDArray[np.int64],

pandas/_libs/tslibs/vectorized.pyx

+9-4
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ def ints_to_pydatetime(
100100
tzinfo tz=None,
101101
BaseOffset freq=None,
102102
bint fold=False,
103-
str box="datetime"
103+
str box="datetime",
104+
NPY_DATETIMEUNIT reso=NPY_FR_ns,
104105
) -> np.ndarray:
105106
# stamps is int64, arbitrary ndim
106107
"""
@@ -126,12 +127,14 @@ def ints_to_pydatetime(
126127
* If time, convert to datetime.time
127128
* If Timestamp, convert to pandas.Timestamp
128129

130+
reso : NPY_DATETIMEUNIT, default NPY_FR_ns
131+
129132
Returns
130133
-------
131134
ndarray[object] of type specified by box
132135
"""
133136
cdef:
134-
Localizer info = Localizer(tz, reso=NPY_FR_ns)
137+
Localizer info = Localizer(tz, reso=reso)
135138
int64_t utc_val, local_val
136139
Py_ssize_t i, n = stamps.size
137140
Py_ssize_t pos = -1 # unused, avoid not-initialized warning
@@ -179,10 +182,12 @@ def ints_to_pydatetime(
179182
# find right representation of dst etc in pytz timezone
180183
new_tz = tz._tzinfos[tz._transition_info[pos]]
181184

182-
dt64_to_dtstruct(local_val, &dts)
185+
pandas_datetime_to_datetimestruct(local_val, reso, &dts)
183186

184187
if use_ts:
185-
res_val = create_timestamp_from_ts(utc_val, dts, new_tz, freq, fold)
188+
res_val = create_timestamp_from_ts(
189+
utc_val, dts, new_tz, freq, fold, reso=reso
190+
)
186191
elif use_pydt:
187192
res_val = datetime(
188193
dts.year, dts.month, dts.day, dts.hour, dts.min, dts.sec, dts.us,

pandas/core/arrays/datetimelike.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -424,17 +424,18 @@ def astype(self, dtype, copy: bool = True):
424424

425425
if is_object_dtype(dtype):
426426
if self.dtype.kind == "M":
427+
self = cast("DatetimeArray", self)
427428
# *much* faster than self._box_values
428429
# for e.g. test_get_loc_tuple_monotonic_above_size_cutoff
429-
i8data = self.asi8.ravel()
430+
i8data = self.asi8
430431
converted = ints_to_pydatetime(
431432
i8data,
432-
# error: "DatetimeLikeArrayMixin" has no attribute "tz"
433-
tz=self.tz, # type: ignore[attr-defined]
433+
tz=self.tz,
434434
freq=self.freq,
435435
box="timestamp",
436+
reso=self._reso,
436437
)
437-
return converted.reshape(self.shape)
438+
return converted
438439

439440
elif self.dtype.kind == "m":
440441
return ints_to_pytimedelta(self._ndarray, box=True)

pandas/core/arrays/datetimes.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,11 @@ def __iter__(self):
653653
start_i = i * chunksize
654654
end_i = min((i + 1) * chunksize, length)
655655
converted = ints_to_pydatetime(
656-
data[start_i:end_i], tz=self.tz, freq=self.freq, box="timestamp"
656+
data[start_i:end_i],
657+
tz=self.tz,
658+
freq=self.freq,
659+
box="timestamp",
660+
reso=self._reso,
657661
)
658662
yield from converted
659663

@@ -1044,7 +1048,7 @@ def to_pydatetime(self) -> npt.NDArray[np.object_]:
10441048
-------
10451049
datetimes : ndarray[object]
10461050
"""
1047-
return ints_to_pydatetime(self.asi8, tz=self.tz)
1051+
return ints_to_pydatetime(self.asi8, tz=self.tz, reso=self._reso)
10481052

10491053
def normalize(self) -> DatetimeArray:
10501054
"""
@@ -1301,7 +1305,7 @@ def time(self) -> npt.NDArray[np.object_]:
13011305
# keeping their timezone and not using UTC
13021306
timestamps = self._local_timestamps()
13031307

1304-
return ints_to_pydatetime(timestamps, box="time")
1308+
return ints_to_pydatetime(timestamps, box="time", reso=self._reso)
13051309

13061310
@property
13071311
def timetz(self) -> npt.NDArray[np.object_]:
@@ -1311,7 +1315,7 @@ def timetz(self) -> npt.NDArray[np.object_]:
13111315
13121316
The time part of the Timestamps.
13131317
"""
1314-
return ints_to_pydatetime(self.asi8, self.tz, box="time")
1318+
return ints_to_pydatetime(self.asi8, self.tz, box="time", reso=self._reso)
13151319

13161320
@property
13171321
def date(self) -> npt.NDArray[np.object_]:
@@ -1326,7 +1330,7 @@ def date(self) -> npt.NDArray[np.object_]:
13261330
# keeping their timezone and not using UTC
13271331
timestamps = self._local_timestamps()
13281332

1329-
return ints_to_pydatetime(timestamps, box="date")
1333+
return ints_to_pydatetime(timestamps, box="date", reso=self._reso)
13301334

13311335
def isocalendar(self) -> DataFrame:
13321336
"""

pandas/tests/arrays/test_datetimes.py

+29
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,35 @@ def test_to_period(self, dta_dti):
126126

127127
tm.assert_extension_array_equal(result, expected)
128128

129+
def test_iter(self, dta):
130+
res = next(iter(dta))
131+
expected = dta[0]
132+
133+
assert type(res) is pd.Timestamp
134+
assert res.value == expected.value
135+
assert res._reso == expected._reso
136+
assert res == expected
137+
138+
def test_astype_object(self, dta):
139+
result = dta.astype(object)
140+
assert all(x._reso == dta._reso for x in result)
141+
assert all(x == y for x, y in zip(result, dta))
142+
143+
def test_to_pydatetime(self, dta_dti):
144+
dta, dti = dta_dti
145+
146+
result = dta.to_pydatetime()
147+
expected = dti.to_pydatetime()
148+
tm.assert_numpy_array_equal(result, expected)
149+
150+
@pytest.mark.parametrize("meth", ["time", "timetz", "date"])
151+
def test_time_date(self, dta_dti, meth):
152+
dta, dti = dta_dti
153+
154+
result = getattr(dta, meth)
155+
expected = getattr(dti, meth)
156+
tm.assert_numpy_array_equal(result, expected)
157+
129158

130159
class TestDatetimeArrayComparisons:
131160
# TODO: merge this into tests/arithmetic/test_datetime64 once it is

0 commit comments

Comments
 (0)