Skip to content

NetCDF Utilities#

Low-level GDAL helpers for attribute reading, no-data extraction, scale/offset handling, time conversion, and dtype utilities.

pyramids.netcdf.utils #

create_time_conversion_func(units, out_format='%Y-%m-%d %H:%M:%S', calendar='standard') #

Create a converter that maps numeric CF time offsets to date strings.

Parses CF-compliant time unit strings (e.g., "days since 1979-01-01") and returns a callable that converts numeric offsets to formatted date strings.

For standard/proleptic_gregorian calendars, uses Python's datetime + timedelta. For non-standard calendars (360_day, noleap, all_leap, julian), uses cftime.num2date() (optional dependency).

Parameters:

Name Type Description Default
units str

CF time unit string in the format "<unit> since <origin>". Supported units are days, hours, minutes, and seconds.

required
out_format str

strftime format for the output strings. Defaults to "%Y-%m-%d %H:%M:%S".

'%Y-%m-%d %H:%M:%S'
calendar str

CF calendar type. Defaults to "standard". Non-standard calendars require the cftime package.

'standard'

Returns:

Name Type Description
Callable Callable

A function that takes a numeric value and returns a formatted date string.

Raises:

Type Description
ValueError

If the unit string cannot be parsed or uses an unsupported time unit.

ImportError

If a non-standard calendar is requested and cftime is not installed.

Examples:

  • Convert day offsets from a 1979 origin:

    >>> from pyramids.netcdf.utils import (
    ...     create_time_conversion_func,
    ... )
    >>> convert = create_time_conversion_func(
    ...     "days since 1979-01-01"
    ... )
    >>> convert(0)
    '1979-01-01 00:00:00'
    >>> convert(365)
    '1980-01-01 00:00:00'
    

  • Use hour-based units with a custom format:

    >>> convert = create_time_conversion_func(
    ...     "hours since 2000-01-01",
    ...     out_format="%Y-%m-%d",
    ... )
    >>> convert(24)
    '2000-01-02'
    >>> convert(0)
    '2000-01-01'
    

See Also

_parse_units_origin: Parses the unit string.

Source code in src/pyramids/netcdf/utils.py
def create_time_conversion_func(
    units: str,
    out_format: str = "%Y-%m-%d %H:%M:%S",
    calendar: str = "standard",
) -> Callable:
    """Create a converter that maps numeric CF time offsets to date strings.

    Parses CF-compliant time unit strings (e.g.,
    ``"days since 1979-01-01"``) and returns a callable that
    converts numeric offsets to formatted date strings.

    For standard/proleptic_gregorian calendars, uses Python's
    ``datetime`` + ``timedelta``. For non-standard calendars
    (``360_day``, ``noleap``, ``all_leap``, ``julian``), uses
    ``cftime.num2date()`` (optional dependency).

    Args:
        units: CF time unit string in the format
            ``"<unit> since <origin>"``. Supported units are
            days, hours, minutes, and seconds.
        out_format: strftime format for the output strings.
            Defaults to ``"%Y-%m-%d %H:%M:%S"``.
        calendar: CF calendar type. Defaults to ``"standard"``.
            Non-standard calendars require the ``cftime`` package.

    Returns:
        Callable: A function that takes a numeric value and
            returns a formatted date string.

    Raises:
        ValueError: If the unit string cannot be parsed or
            uses an unsupported time unit.
        ImportError: If a non-standard calendar is requested
            and ``cftime`` is not installed.

    Examples:
        - Convert day offsets from a 1979 origin:
            ```python
            >>> from pyramids.netcdf.utils import (
            ...     create_time_conversion_func,
            ... )
            >>> convert = create_time_conversion_func(
            ...     "days since 1979-01-01"
            ... )
            >>> convert(0)
            '1979-01-01 00:00:00'
            >>> convert(365)
            '1980-01-01 00:00:00'

            ```

        - Use hour-based units with a custom format:
            ```python
            >>> convert = create_time_conversion_func(
            ...     "hours since 2000-01-01",
            ...     out_format="%Y-%m-%d",
            ... )
            >>> convert(24)
            '2000-01-02'
            >>> convert(0)
            '2000-01-01'

            ```

    See Also:
        _parse_units_origin: Parses the unit string.
    """
    converter = None

    if calendar.lower() not in (
        "standard", "proleptic_gregorian", "gregorian"
    ):
        try:
            import cftime  # noqa: F811 - optional dep, inline import required
        except ImportError:
            raise ImportError(
                f"Calendar '{calendar}' requires the cftime package. "
                f"Install it with: pixi add cftime"
            )

        def convert_cftime(value):
            dt = cftime.num2date(value, units, calendar)
            return dt.strftime(out_format)

        converter = convert_cftime
    else:
        unit, origin = _parse_units_origin(units)

        if unit.startswith("day"):
            scale = timedelta(days=1)
        elif unit.startswith("hour"):
            scale = timedelta(hours=1)
        elif unit.startswith("min"):
            scale = timedelta(minutes=1)
        elif unit.startswith("sec"):
            scale = timedelta(seconds=1)
        else:
            raise ValueError(f"Unsupported time unit: {unit!r}")

        def convert(value):
            dt = origin + value * scale
            return dt.strftime(out_format)

        converter = convert

    return converter