From df7de848353ab0e8f9824f6e1666e77b0783deb5 Mon Sep 17 00:00:00 2001 From: Jesse Cusack Date: Tue, 17 Mar 2026 10:54:15 -0700 Subject: [PATCH] output file names with times --- src/glide/cli.py | 23 ++++++++++++++++++++++- tests/test_cli.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/glide/cli.py b/src/glide/cli.py index 5798456..ed9948c 100644 --- a/src/glide/cli.py +++ b/src/glide/cli.py @@ -3,6 +3,7 @@ import functools import inspect import logging +from datetime import datetime, timezone from importlib.metadata import version from pathlib import Path @@ -41,6 +42,13 @@ def wrapper(*args, **kwargs): return wrapper +def generate_ioos_filename(ds, glider_name: str) -> str: + first_time = float(ds["time"].values[0]) + dt = datetime.fromtimestamp(first_time, tz=timezone.utc) + timestamp = dt.strftime("%Y%m%dT%H%M%SZ") + return f"{glider_name}_{timestamp}.nc" + + # Commonly used argument annotations _config_annotation = Annotated[ str | None, @@ -129,6 +137,14 @@ def l2( "output.", ), ] = False, + glider_name: Annotated[ + str | None, + typer.Option( + "--glider", + "-g", + help="Glider name for IOOS-style filename when output is a directory.", + ), + ] = None, ) -> None: """ Generate L2 data from L1 data. @@ -159,7 +175,12 @@ def l2( out.encoding["unlimited_dims"] = {} - out.to_netcdf(out_file) + out_path = Path(out_file) + if out_path.is_dir(): + name = glider_name or conf["globals"]["trajectory"]["name"].split("_")[-1] + out_path = out_path / generate_ioos_filename(out, name) + + out.to_netcdf(out_path) if riot_csv: from .riot_csv_writer import write_riot_csv diff --git a/tests/test_cli.py b/tests/test_cli.py index 4050e83..32fc047 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -27,6 +27,44 @@ def test_l2() -> None: assert result.exit_code == 0 +def test_l2_directory_output() -> None: + import re + import tempfile + from datetime import datetime, timezone + + data_dir = Path(str(resources.files("tests").joinpath("data"))) + sbd_files = sorted(data_dir.glob("*.sbd.csv")) + + with tempfile.TemporaryDirectory() as tmpdir: + for sbd_file in sbd_files: + tbd_file = sbd_file.with_suffix("").with_suffix(".tbd.csv") + if not tbd_file.exists(): + continue + + glider = sbd_file.name.split("-")[0].split(".")[0] + result = runner.invoke( + app, ["l2", str(sbd_file), str(tbd_file), "-o", tmpdir, "-g", glider] + ) + assert result.exit_code == 0, f"Failed for {sbd_file.name}: {result.output}" + + nc_files = list(Path(tmpdir).glob("*.nc")) + assert len(nc_files) == len(sbd_files) + + for nc_file in nc_files: + assert re.match(r"\w+_\d{8}T\d{6}Z\.nc", nc_file.name) + + ds = xr.open_dataset(nc_file) + first_time = ds["time"].values[0] + ds.close() + + dt = np.datetime64(first_time, "s").astype("datetime64[s]").astype(datetime) + dt = dt.replace(tzinfo=timezone.utc) + expected_ts = dt.strftime("%Y%m%dT%H%M%SZ") + assert expected_ts in nc_file.name, ( + f"Timestamp mismatch: expected {expected_ts} in {nc_file.name}" + ) + + def test_l3() -> None: l2_file = str(resources.files("tests").joinpath("data/slocum.l2.nc")) out_file = str(resources.files("tests").joinpath("data/slocum.l3.nc"))