diff --git a/docs/user_guide/convert_mom6.ipynb b/docs/user_guide/convert_mom6.ipynb index d67f9a64c..738a45d92 100644 --- a/docs/user_guide/convert_mom6.ipynb +++ b/docs/user_guide/convert_mom6.ipynb @@ -149,6 +149,9 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.14" + }, + "mystnb": { + "execution_in_temp": true } }, "nbformat": 4, diff --git a/docs/user_guide/fieldsets_grids.ipynb b/docs/user_guide/fieldsets_grids.ipynb index a6fa91b17..ea19d640e 100644 --- a/docs/user_guide/fieldsets_grids.ipynb +++ b/docs/user_guide/fieldsets_grids.ipynb @@ -12,7 +12,7 @@ "Parcels can natively handle two styles of grids; structured and unstructured, where Parcels `Field` objects exist on a (structured) {py:obj}`parcels.XGrid` and conform to [SGRID](https://sgrid.github.io/sgrid/) conventions, or on a (unstructured) {py:obj}`parcels.UxGrid` and conform to [UGRID](https://ugrid-conventions.github.io/ugrid-conventions/) conventions. Here we describe these grids on a conceptual level.\n", "\n", "```{note}\n", - "Parcels comes ready with a range of `convert` functions which will convert your `xarray` dataset into a SGRID or UGRID compliant dataset. See [this list](group_convert_models.md) for all the models we have `convert` functions for. If your model is not on that list, you can [create your own `convert` function](./convert_writing_functions.md), or reach out on our discussion board for help.\n", + "Parcels comes ready with a range of `convert` functions which will convert your `xarray` dataset into a SGRID or UGRID compliant dataset. See [this list](group_convert_ocean_models.md) for all the ocean models we have `convert` functions for, and [this list](group_convert_atmospheric_models.md) for all the atmosphere models we have `convert` functions for. If your model is not on that list, you can [create your own `convert` function](./convert_writing_functions.md), or reach out on our discussion board for help.\n", "```\n", "\n", "Under the hood, every `Field` in a `FieldSet` has a `grid` attribute. This `grid` stores the spatial and temporal information of the Field coordinates. The number of Grids in a FieldSet is thus always smaller or equal to the number of Field objects; and this is what the \"grid number\" column in {py:func}`parcels.FieldSet.describe()` refers to.\n", diff --git a/src/parcels/_core/utils/time.py b/src/parcels/_core/utils/time.py index fbe2056db..fdde89cf3 100644 --- a/src/parcels/_core/utils/time.py +++ b/src/parcels/_core/utils/time.py @@ -221,6 +221,8 @@ def float_to_datelike(dt: float, time_interval) -> np.datetime64 | np.timedelta6 result = np.timedelta64(int(dt), "s") + time_interval.left if isinstance(result, cftime.datetime): return result + elif isinstance(result, np.timedelta64): + return result.astype("timedelta64[s]") return result.astype("datetime64[s]") else: return np.timedelta64(int(dt), "s") diff --git a/tests/utils/test_time.py b/tests/utils/test_time.py index 180bc3102..bb7ff2789 100644 --- a/tests/utils/test_time.py +++ b/tests/utils/test_time.py @@ -11,6 +11,7 @@ from parcels._core.utils.time import ( TimeInterval, _get_cf_attrs, + float_to_datelike, maybe_convert_python_timedelta_to_numpy, timedelta_to_float, ) @@ -175,6 +176,48 @@ def test_timedelta_to_float_exceptions(): timedelta_to_float("invalid_type") +@pytest.mark.parametrize( + "left,right,offset,expected", + [ + (np.timedelta64(10500, "ms"), np.timedelta64(20, "s"), 2.9, np.timedelta64(12, "s")), + (np.timedelta64(3, "m"), np.timedelta64(5, "m"), -4.7, np.timedelta64(176, "s")), + ], +) +def test_float_to_datelike_timedelta_interval(left, right, offset, expected): + result = float_to_datelike(offset, TimeInterval(left, right)) + + assert isinstance(result, np.timedelta64) + assert result.dtype == np.dtype("timedelta64[s]") + assert result == expected + + +@pytest.mark.parametrize( + "interval,expected", + [ + (None, np.timedelta64(2, "s")), + ( + TimeInterval(np.datetime64("2023-01-01"), np.datetime64("2023-01-02")), + np.datetime64("2023-01-01T00:00:02", "s"), + ), + ( + TimeInterval( + cftime_datetime(2023, 1, 1, calendar="360_day"), cftime_datetime(2023, 1, 2, calendar="360_day") + ), + cftime_datetime(2023, 1, 1, 0, 0, 2, calendar="360_day"), + ), + ], +) +def test_float_to_datelike_other_intervals(interval, expected): + result = float_to_datelike(2.9, interval) + + assert isinstance(result, type(expected)) + assert result == expected + if isinstance(expected, (np.datetime64, np.timedelta64)): + assert result.dtype == expected.dtype + elif isinstance(expected, cftime_datetime): + assert result.calendar == expected.calendar + + @given(pst.time.datetime_various()) def test_datetime_get_cf_attrs(dt): attrs = _get_cf_attrs(dt)