diff --git a/tests/test_temporal.py b/tests/test_temporal.py index af09c47b..079dc340 100644 --- a/tests/test_temporal.py +++ b/tests/test_temporal.py @@ -419,19 +419,11 @@ def test_weighted_annual_averages(self): "lon": expected.lon, "time": xr.DataArray( data=np.array( - [ - "2000-01-01T00:00:00.000000000", - "2001-01-01T00:00:00.000000000", - ], - dtype="datetime64[ns]", + [cftime.datetime(2000, 1, 1), cftime.datetime(2001, 1, 1)], ), coords={ "time": np.array( - [ - "2000-01-01T00:00:00.000000000", - "2001-01-01T00:00:00.000000000", - ], - dtype="datetime64[ns]", + [cftime.datetime(2000, 1, 1), cftime.datetime(2001, 1, 1)], ) }, dims=["time"], @@ -470,19 +462,11 @@ def test_weighted_annual_averages_with_chunking(self): "lon": expected.lon, "time": xr.DataArray( data=np.array( - [ - "2000-01-01T00:00:00.000000000", - "2001-01-01T00:00:00.000000000", - ], - dtype="datetime64[ns]", + [cftime.datetime(2000, 1, 1), cftime.datetime(2001, 1, 1)], ), coords={ "time": np.array( - [ - "2000-01-01T00:00:00.000000000", - "2001-01-01T00:00:00.000000000", - ], - dtype="datetime64[ns]", + [cftime.datetime(2000, 1, 1), cftime.datetime(2001, 1, 1)], ) }, dims=["time"], @@ -527,12 +511,11 @@ def test_weighted_seasonal_averages_with_DJF_and_drop_incomplete_seasons(self): "time": xr.DataArray( data=np.array( [ - "2000-04-01T00:00:00.000000000", - "2000-07-01T00:00:00.000000000", - "2000-10-01T00:00:00.000000000", - "2001-01-01T00:00:00.000000000", + cftime.datetime(2000, 4, 1), + cftime.datetime(2000, 7, 1), + cftime.datetime(2000, 10, 1), + cftime.datetime(2001, 1, 1), ], - dtype="datetime64[ns]", ), dims=["time"], attrs={ @@ -577,13 +560,12 @@ def test_weighted_seasonal_averages_with_DJF_without_dropping_incomplete_seasons "time": xr.DataArray( data=np.array( [ - "2000-01-01T00:00:00.000000000", - "2000-04-01T00:00:00.000000000", - "2000-07-01T00:00:00.000000000", - "2000-10-01T00:00:00.000000000", - "2001-01-01T00:00:00.000000000", + cftime.datetime(2000, 1, 1), + cftime.datetime(2000, 4, 1), + cftime.datetime(2000, 7, 1), + cftime.datetime(2000, 10, 1), + cftime.datetime(2001, 1, 1), ], - dtype="datetime64[ns]", ), dims=["time"], attrs={ @@ -626,24 +608,22 @@ def test_weighted_seasonal_averages_with_JFD(self): "time": xr.DataArray( data=np.array( [ - "2000-01-01T00:00:00.000000000", - "2000-04-01T00:00:00.000000000", - "2000-07-01T00:00:00.000000000", - "2000-10-01T00:00:00.000000000", - "2001-01-01T00:00:00.000000000", + cftime.datetime(2000, 1, 1), + cftime.datetime(2000, 4, 1), + cftime.datetime(2000, 7, 1), + cftime.datetime(2000, 10, 1), + cftime.datetime(2001, 1, 1), ], - dtype="datetime64[ns]", ), coords={ "time": np.array( [ - "2000-01-01T00:00:00.000000000", - "2000-04-01T00:00:00.000000000", - "2000-07-01T00:00:00.000000000", - "2000-10-01T00:00:00.000000000", - "2001-01-01T00:00:00.000000000", + cftime.datetime(2000, 1, 1), + cftime.datetime(2000, 4, 1), + cftime.datetime(2000, 7, 1), + cftime.datetime(2000, 10, 1), + cftime.datetime(2001, 1, 1), ], - dtype="datetime64[ns]", ) }, dims=["time"], @@ -692,12 +672,11 @@ def test_weighted_custom_seasonal_averages(self): "time": xr.DataArray( data=np.array( [ - "2000-02-01T00:00:00.000000000", - "2000-05-01T00:00:00.000000000", - "2000-08-01T00:00:00.000000000", - "2001-02-01T00:00:00.000000000", + cftime.datetime(2000, 2, 1), + cftime.datetime(2000, 5, 1), + cftime.datetime(2000, 8, 1), + cftime.datetime(2001, 2, 1), ], - dtype="datetime64[ns]", ), dims=["time"], attrs={ @@ -783,13 +762,12 @@ def test_weighted_monthly_averages(self): "time": xr.DataArray( data=np.array( [ - "2000-01-01T00:00:00.000000000", - "2000-03-01T00:00:00.000000000", - "2000-06-01T00:00:00.000000000", - "2000-09-01T00:00:00.000000000", - "2001-02-01T00:00:00.000000000", + cftime.datetime(2000, 1, 1), + cftime.datetime(2000, 3, 1), + cftime.datetime(2000, 6, 1), + cftime.datetime(2000, 9, 1), + cftime.datetime(2001, 2, 1), ], - dtype="datetime64[ns]", ), dims=["time"], attrs={ @@ -833,13 +811,12 @@ def test_weighted_monthly_averages_with_masked_data(self): "time": xr.DataArray( data=np.array( [ - "2000-01-01T00:00:00.000000000", - "2000-03-01T00:00:00.000000000", - "2000-06-01T00:00:00.000000000", - "2000-09-01T00:00:00.000000000", - "2001-02-01T00:00:00.000000000", + cftime.datetime(2000, 1, 1), + cftime.datetime(2000, 3, 1), + cftime.datetime(2000, 6, 1), + cftime.datetime(2000, 9, 1), + cftime.datetime(2001, 2, 1), ], - dtype="datetime64[ns]", ), dims=["time"], attrs={ @@ -876,13 +853,12 @@ def test_weighted_daily_averages(self): "time": xr.DataArray( data=np.array( [ - "2000-01-16T00:00:00.000000000", - "2000-03-16T00:00:00.000000000", - "2000-06-16T00:00:00.000000000", - "2000-09-16T00:00:00.000000000", - "2001-02-15T00:00:00.000000000", + cftime.datetime(2000, 1, 16), + cftime.datetime(2000, 3, 16), + cftime.datetime(2000, 6, 16), + cftime.datetime(2000, 9, 16), + cftime.datetime(2001, 2, 15), ], - dtype="datetime64[ns]", ), dims=["time"], attrs={ @@ -917,7 +893,24 @@ def test_weighted_hourly_averages(self): coords={ "lat": expected.lat, "lon": expected.lon, - "time": ds.time, + "time": xr.DataArray( + data=np.array( + [ + cftime.datetime(2000, 1, 16, 12), + cftime.datetime(2000, 3, 16, 12), + cftime.datetime(2000, 6, 16, 0), + cftime.datetime(2000, 9, 16, 0), + cftime.datetime(2001, 2, 15, 12), + ], + ), + dims=["time"], + attrs={ + "axis": "T", + "long_name": "time", + "standard_name": "time", + "bounds": "time_bnds", + }, + ), }, dims=["time", "lat", "lon"], attrs={ diff --git a/xcdat/temporal.py b/xcdat/temporal.py index 0b4fee79..cfcbcb68 100644 --- a/xcdat/temporal.py +++ b/xcdat/temporal.py @@ -1306,17 +1306,14 @@ def _drop_obsolete_columns(self, df_season: pd.DataFrame) -> pd.DataFrame: return df_season def _convert_df_to_dt(self, df: pd.DataFrame) -> np.ndarray: - """Converts a DataFrame of datetime components to datetime objects. + """Converts a DataFrame of datetime components to cftime datetime + objects. datetime objects require at least a year, month, and day value. However, some modes and time frequencies don't require year, month, and/or day for grouping. For these cases, use default values of 1 in order to meet this datetime requirement. - If the default value of 1 is used for the years, datetime objects - must be created using `cftime.datetime` because year 1 is outside the - Timestamp-valid range. - Parameters ---------- df : pd.DataFrame @@ -1325,11 +1322,12 @@ def _convert_df_to_dt(self, df: pd.DataFrame) -> np.ndarray: Returns ------- np.ndarray - A numpy ndarray of datetime.datetime or cftime.datetime objects. + A numpy ndarray of cftime.datetime objects. Notes ----- Refer to [3]_ and [4]_ for more information on Timestamp-valid range. + We use cftime.datetime objects to avoid these time range issues. References ---------- @@ -1344,18 +1342,12 @@ def _convert_df_to_dt(self, df: pd.DataFrame) -> np.ndarray: if component not in df_new.columns: df_new[component] = default_val - year_is_unused = self._mode in ["climatology", "departures"] or ( - self._mode == "average" and self._freq != "year" - ) - if year_is_unused: - dates = [ - cftime.datetime(year, month, day, hour) - for year, month, day, hour in zip( - df_new.year, df_new.month, df_new.day, df_new.hour - ) - ] - else: - dates = pd.to_datetime(df_new) + dates = [ + cftime.datetime(year, month, day, hour) + for year, month, day, hour in zip( + df_new.year, df_new.month, df_new.day, df_new.hour + ) + ] return np.array(dates)