Skip to content

Commit 52283c6

Browse files
Adapt to change in behavior of pandas.Timestamp constructor (#10944)
* Adapt to change in behavior of pandas.Timestamp constructor * Fix typing * Use safer return logic Co-authored-by: Stephan Hoyer <[email protected]> --------- Co-authored-by: Stephan Hoyer <[email protected]>
1 parent 6e82a3a commit 52283c6

File tree

2 files changed

+22
-4
lines changed

2 files changed

+22
-4
lines changed

xarray/coding/times.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@
9898
"scale_factor",
9999
]
100100

101+
_ORDERED_PANDAS_TIME_RESOLUTIONS: list[PDDatetimeUnitOptions] = ["s", "ms", "us", "ns"]
102+
101103

102104
def _is_standard_calendar(calendar: str) -> bool:
103105
return calendar.lower() in _STANDARD_CALENDARS
@@ -293,6 +295,21 @@ def _maybe_strip_tz_from_timestamp(date: pd.Timestamp) -> pd.Timestamp:
293295
return date
294296

295297

298+
def _cast_timestamp_to_coarsest_resolution(timestamp: pd.Timestamp) -> pd.Timestamp:
299+
# Cast timestamp to the coarsest resolution that can be used without
300+
# changing its value. If provided a string, the pandas.Timestamp
301+
# constructor used to automatically infer this from the resolution of the
302+
# string, but this behavior was changed in pandas-dev/pandas#62801. This
303+
# function allows us to approximately restore the old behavior in a way
304+
# that is perhaps more consistent with how we infer the resolution of the
305+
# data values themselves.
306+
for unit in _ORDERED_PANDAS_TIME_RESOLUTIONS:
307+
coarsest_timestamp = timestamp.as_unit(unit)
308+
if coarsest_timestamp == timestamp:
309+
return coarsest_timestamp
310+
return timestamp
311+
312+
296313
def _unpack_time_unit_and_ref_date(
297314
units: str,
298315
) -> tuple[NPDatetimeUnitOptions, pd.Timestamp]:
@@ -301,6 +318,7 @@ def _unpack_time_unit_and_ref_date(
301318
time_unit, _ref_date = _unpack_netcdf_time_units(units)
302319
time_unit = _netcdf_to_numpy_timeunit(time_unit)
303320
ref_date = pd.Timestamp(_ref_date)
321+
ref_date = _cast_timestamp_to_coarsest_resolution(ref_date)
304322
ref_date = _maybe_strip_tz_from_timestamp(ref_date)
305323
return time_unit, ref_date
306324

@@ -442,8 +460,8 @@ def _check_higher_resolution(
442460
time_unit: PDDatetimeUnitOptions,
443461
) -> tuple[np.ndarray, PDDatetimeUnitOptions]:
444462
"""Iterate until fitting resolution found."""
445-
res: list[PDDatetimeUnitOptions] = ["s", "ms", "us", "ns"]
446-
new_units = res[res.index(time_unit) :]
463+
index = _ORDERED_PANDAS_TIME_RESOLUTIONS.index(time_unit)
464+
new_units = _ORDERED_PANDAS_TIME_RESOLUTIONS[index:]
447465
for new_time_unit in new_units:
448466
if not ((np.unique(flat_num_dates % 1) > 0).any() and new_time_unit != "ns"):
449467
break

xarray/tests/test_variable.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,7 @@ def test_numpy_same_methods(self):
10861086
[
10871087
(np.datetime64("2000-01-01"), "s"),
10881088
(
1089-
pd.Timestamp("2000-01-01T00"),
1089+
pd.Timestamp("2000-01-01T00").as_unit("s"),
10901090
"s" if has_pandas_3 else "ns",
10911091
),
10921092
(
@@ -1128,7 +1128,7 @@ def test_0d_str(self):
11281128
assert v.values == "foo".encode("ascii")
11291129

11301130
def test_0d_datetime(self):
1131-
v = Variable([], pd.Timestamp("2000-01-01"))
1131+
v = Variable([], pd.Timestamp("2000-01-01").as_unit("s"))
11321132
expected_unit = "s" if has_pandas_3 else "ns"
11331133
assert v.dtype == np.dtype(f"datetime64[{expected_unit}]")
11341134
assert v.values == np.datetime64("2000-01-01", expected_unit) # type: ignore[call-overload]

0 commit comments

Comments
 (0)