rivia.utils

Shared helpers: path handling, validation, logging.

Shared helpers (path handling, validation, logging).

rivia.utils.assert_path_writable(path)[source]

Raise PermissionError early if path cannot be written.

Tests the actual filesystem operations that a write requires:

  • If the file exists: attempts a rename to a temporary name and back. On Windows this requires DELETE access on the source — the same permission GDAL needs to delete-and-rewrite the file. GIS applications (ArcGIS, QGIS, RASMapper) typically hold files open without FILE_SHARE_DELETE, so the rename fails if any of them have the file open, even on network/SMB shares where CreateFileW checks are unreliable.

  • If the file does not exist: creates and immediately removes a zero-byte sentinel in the parent directory to verify write permission.

Parameters:

path (str | Path) – Output file path to check.

Raises:

PermissionError – If the path cannot be written, with a message identifying the file and suggesting the user close it in any open application.

Return type:

None

rivia.utils.check_sim_date(date)[source]

Raise ValueError if date is not in DDMONYYYY format.

The month abbreviation is case-insensitive (e.g. "01jan2020" is valid).

Return type:

None

Parameters:

date (str)

rivia.utils.check_sim_time(time_str)[source]

Raise ValueError if time_str is not HHMM or HHMMSS with HH <= 24.

Return type:

None

Parameters:

time_str (str)

rivia.utils.format_hec_datetime(d)[source]

Format a datetime.datetime as a HEC-RAS "DDMONYYYY HH:MM:SS" string.

This is the canonical format used in HEC-RAS HDF5 attributes and runtime log timestamps (space separator, colon-separated 24-hour time, uppercase month abbreviation).

Parameters:

d (datetime.datetime)

Returns:

e.g. "01OCT2024 00:00:02"

Return type:

str

Examples

>>> import datetime as dt
>>> format_hec_datetime(dt.datetime(2024, 10, 1, 0, 0, 2))
'01OCT2024 00:00:02'
rivia.utils.log_call(level=20)[source]

Decorator that logs when a function is called.

Parameters:

level (int) – logging level constant (e.g. logging.INFO, logging.DEBUG). Defaults to logging.INFO.

Examples

>>> from rivia.utils import log_call
>>> import logging
>>> @log_call(logging.INFO)
... def my_func(): ...
rivia.utils.normalize_sim_end_time(date, time)[source]

Normalize a HEC-RAS simulation end (date, time) pair.

HEC-RAS represents midnight as "2400" on the ending day, but can also produce "0000" on the following day for the same instant. Restart filenames always use the "2400" form, so when time is "0000" this function rolls the date back by one day and substitutes "2400".

Parameters:
  • date (str) – Simulation date in DDMONYYYY format (e.g. "02JAN2026").

  • time (str) – Simulation time in HHMM format (e.g. "0000" or "1200").

Returns:

(date, time) — adjusted when time is "0000", unchanged otherwise.

Return type:

tuple[str, str]

Examples

>>> normalize_sim_end_time("02JAN2026", "0000")
('01JAN2026', '2400')
>>> normalize_sim_end_time("02JAN2026", "1200")
('02JAN2026', '1200')
>>> normalize_sim_end_time("01JAN2026", "2400")
('01JAN2026', '2400')
rivia.utils.normalize_sim_start_time(date, time)[source]

Normalize a HEC-RAS simulation start (date, time) pair.

HEC-RAS represents midnight as "2400" on the ending day, but the same instant used as a start time should be expressed as "0000" on the following day. When time is "2400" this function advances the date by one day and substitutes "0000".

Parameters:
  • date (str) – Simulation date in DDMONYYYY format (e.g. "01JAN2026").

  • time (str) – Simulation time in HHMM format (e.g. "2400" or "1200").

Returns:

(date, time) — adjusted when time is "2400", unchanged otherwise.

Return type:

tuple[str, str]

Examples

>>> normalize_sim_start_time("01JAN2026", "2400")
('02JAN2026', '0000')
>>> normalize_sim_start_time("01JAN2026", "1200")
('01JAN2026', '1200')
>>> normalize_sim_start_time("01JAN2026", "0000")
('01JAN2026', '0000')
rivia.utils.parse_hec_datetime(text, fmt=None)[source]

Parse any HEC-RAS combined datetime string.

The date part is always DDMONYYYY (9 characters), followed by a separator (space or comma), then a time part in one of the supported variants:

  • HH:MM:SS — 24-hour with colons (HDF timestamps, runtime log lines)

  • HH:MM:SS AM/PM — 12-hour with colons (Simulation started at: lines)

  • HHMMSS — 6-digit, no colons (space or comma separator)

  • HHMM — 4-digit, no colons (space or comma separator)

HH=24 is handled in all variants: treated as 00:XX:XX on the following day, matching HEC-RAS end-of-day midnight convention.

Parameters:
  • text (str) – HEC-RAS datetime string in any supported format.

  • fmt (str, optional) – strptime format string. When provided, skips auto-detection and uses this format directly. HH=24 is still handled unconditionally because the hour always occupies positions 10–11 in the raw string.

Return type:

datetime.datetime

Raises:

ValueError – If the string is too short, has an unsupported separator, or uses an unrecognised time variant (auto-detect path only).

Examples

>>> parse_hec_datetime("01OCT2024 00:00:02")
datetime.datetime(2024, 10, 1, 0, 0, 2)
>>> parse_hec_datetime("01JAN2026 24:00:00")
datetime.datetime(2026, 1, 2, 0, 0)
>>> parse_hec_datetime("01JAN2026,2400")
datetime.datetime(2026, 1, 2, 0, 0)
>>> parse_hec_datetime("01JAN2026 0002", fmt="%d%b%Y %H%M")
datetime.datetime(2026, 1, 1, 0, 2)
rivia.utils.parse_interval(text)[source]

Parse a HEC-RAS interval string and return a datetime.timedelta.

HEC-RAS writes interval strings as a number immediately followed by a unit abbreviation, with optional whitespace between them, e.g. '20SEC', '5MIN', '1HR', '1HOUR', '1DAY', '2WEEK'.

Supported units (case-insensitive):

Unit

Timedelta

SEC

timedelta(seconds=n)

MIN

timedelta(minutes=n)

HR

timedelta(hours=n)

HOUR

timedelta(hours=n)

DAY

timedelta(days=n)

WEEK

timedelta(weeks=n)

MONTH

timedelta(days=n*30) (approximate)

YEAR

timedelta(days=n*365) (approximate)

Parameters:

text (str | bytes) – Raw interval string from a HEC-RAS HDF attribute or plan file. Bytes are decoded as UTF-8 before parsing.

Return type:

datetime.timedelta

Raises:

ValueError – If text does not match the expected <number><unit> format or the unit is not recognised.

rivia.utils.timed(level=35)[source]

Decorator that logs the elapsed wall-clock time of a function call.

Parameters:

level (int) – logging level constant (e.g. logging.INFO, logging.DEBUG). Defaults to logging.DEBUG.

Examples

>>> from rivia.utils import timed
>>> import logging
>>> @timed(logging.INFO)
... def my_func(): ...

Submodules

fs

Filesystem utilities.

helpers

Shared helper functions used across rivia subpackages.