diff --git a/src/parcels/_core/particlefile.py b/src/parcels/_core/particlefile.py index 50bdf309c..2acfe112b 100644 --- a/src/parcels/_core/particlefile.py +++ b/src/parcels/_core/particlefile.py @@ -275,7 +275,7 @@ def read_particlefile(path: PathLike, decode_times: bool = True) -> pd.DataFrame values = table.column("t").to_numpy() var = xr.Variable(("t",), values, attrs) - values = xr.coders.CFDatetimeCoder(time_unit="s").decode(var).values + values = xr.coders.CFDatetimeCoder(time_unit="ns").decode(var).values if "since" in attrs["units"]: values = values.astype("datetime64[ns]") df = df.with_columns(pl.Series("t", values, dtype=pl.Datetime("ns"))) diff --git a/tests/test_particlefile.py b/tests/test_particlefile.py index d4ed34e34..72a16d997 100755 --- a/tests/test_particlefile.py +++ b/tests/test_particlefile.py @@ -344,6 +344,24 @@ def Update_lon(particles, fieldset): # pragma: no cover assert np.allclose(pset.x, 0.6) +@pytest.mark.parametrize("dt", [100, 200]) +def test_subsecond_outputdt(fieldset, dt, tmp_parquet): + """Test that outputdt can be subsecond and that the output times are correct.""" + + def Update_lon(particles, fieldset): # pragma: no cover + particles.dx += dt / 1000.0 # Move at a rate of 1 unit per second + + pset = ParticleSet(fieldset, x=[0], y=[0]) + ofile = ParticleFile(tmp_parquet, outputdt=np.timedelta64(dt, "ms")) + pset.execute(Update_lon, runtime=np.timedelta64(1, "s"), dt=np.timedelta64(dt, "ms"), output_file=ofile) + + df = parcels.read_particlefile(tmp_parquet) + np.testing.assert_allclose(df["x"], np.arange(0, 1 + 1e-6, dt / 1000.0), atol=1e-6) + expected_t = np.arange(0, 1001, dt).astype("timedelta64[ms]") + elapsed_t = (df["t"] - df["t"].min()).to_numpy().astype("timedelta64[ms]") + np.testing.assert_allclose(elapsed_t, expected_t, atol=1) + + def test_correct_misaligned_outputdt_dt(fieldset, tmp_parquet): """Testing that outputdt does not need to be a multiple of dt."""