From ffcc36d825858b0c4c50e75c6b6e06fbc71e387e Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Thu, 21 Nov 2024 20:53:22 +0100 Subject: [PATCH 1/3] Dataclass instead of tuple --- mikeio/generic.py | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/mikeio/generic.py b/mikeio/generic.py index 555467435..8e877be52 100644 --- a/mikeio/generic.py +++ b/mikeio/generic.py @@ -1,6 +1,7 @@ """Generic functions for working with all types of dfs files.""" from __future__ import annotations +from dataclasses import dataclass import math import os import pathlib @@ -597,10 +598,7 @@ def extract( is_layered_dfsu = dfs_i.ItemInfo[0].Name == "Z coordinate" - file_start_new, start_step, start_sec, end_step, end_sec = _parse_start_end( - dfs_i.FileInfo.TimeAxis, start, end - ) - timestep = _parse_step(dfs_i.FileInfo.TimeAxis, step) + time = _parse_time(dfs_i.FileInfo.TimeAxis, start, end, step) item_numbers = _valid_item_numbers( dfs_i.ItemInfo, items, ignore_first=is_layered_dfsu ) @@ -612,28 +610,28 @@ def extract( dfs_o = _clone( str(infilename), str(outfilename), - start_time=file_start_new, - timestep=timestep, + start_time=time.file_start_new, + timestep=time.timestep, items=item_numbers, ) file_start_shift = 0 - if file_start_new is not None: + if time.file_start_new is not None: file_start_orig = dfs_i.FileInfo.TimeAxis.StartDateTime - file_start_shift = (file_start_new - file_start_orig).total_seconds() + file_start_shift = (time.file_start_new - file_start_orig).total_seconds() timestep_out = -1 - for timestep in range(start_step, end_step, step): + for timestep in range(time.start_step, time.end_step, step): for item_out, item in enumerate(item_numbers): itemdata = dfs_i.ReadItemTimeStep((item + 1), timestep) time_sec = itemdata.Time - if time_sec > end_sec: + if time_sec > time.end_sec: dfs_i.Close() dfs_o.Close() return - if time_sec >= start_sec: + if time_sec >= time.start_sec: if item == item_numbers[0]: timestep_out = timestep_out + 1 time_sec_out = time_sec - file_start_shift @@ -647,11 +645,24 @@ def extract( dfs_o.Close() -def _parse_start_end( +@dataclass +class TimeInfo: + "Parsed time information." + + file_start_new: datetime | None + start_step: int + start_sec: float + end_step: int + end_sec: float + timestep: float | None + + +def _parse_time( time_axis: TimeAxis, start: int | float | str | datetime, end: int | float | str | datetime, -) -> tuple[datetime | None, int, float, int, float]: # TODO better return type + step: int, +) -> TimeInfo: """Helper function for parsing start and end arguments.""" n_time_steps = time_axis.NumberOfTimeSteps file_start_datetime = time_axis.StartDateTime @@ -727,7 +738,9 @@ def _parse_start_end( if start_sec > file_start_sec: file_start_new = file_start_datetime + timedelta(seconds=start_sec) - return file_start_new, start_step, start_sec, end_step, end_sec + timestep = _parse_step(time_axis, step) + + return TimeInfo(file_start_new, start_step, start_sec, end_step, end_sec, timestep) def _parse_step(time_axis: TimeAxis, step: int) -> float | None: From ab22954b4cbfc395200931ea3f46e81e5db2c2f4 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Fri, 22 Nov 2024 08:05:19 +0100 Subject: [PATCH 2/3] Expand TimeInfo --- mikeio/generic.py | 196 ++++++++++++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 86 deletions(-) diff --git a/mikeio/generic.py b/mikeio/generic.py index 8e877be52..8c8b9fa31 100644 --- a/mikeio/generic.py +++ b/mikeio/generic.py @@ -22,6 +22,7 @@ DfsNonEqTimeAxis, DfsEqCalendarAxis, DfsNonEqCalendarAxis, + TimeAxisType, ) from mikecore.DfsFileFactory import DfsFileFactory from mikecore.eum import eumQuantity @@ -598,7 +599,7 @@ def extract( is_layered_dfsu = dfs_i.ItemInfo[0].Name == "Z coordinate" - time = _parse_time(dfs_i.FileInfo.TimeAxis, start, end, step) + time = TimeInfo.parse(dfs_i.FileInfo.TimeAxis, start, end, step) item_numbers = _valid_item_numbers( dfs_i.ItemInfo, items, ignore_first=is_layered_dfsu ) @@ -647,7 +648,24 @@ def extract( @dataclass class TimeInfo: - "Parsed time information." + """Parsed time information. + + Attributes + ---------- + file_start_new : datetime | None + new start time for the new file + start_step : int + start step + start_sec : float + start time in seconds + end_step : int + end step + end_sec : float + end time in seconds + timestep : float | None + timestep in seconds + + """ file_start_new: datetime | None start_step: int @@ -656,92 +674,98 @@ class TimeInfo: end_sec: float timestep: float | None - -def _parse_time( - time_axis: TimeAxis, - start: int | float | str | datetime, - end: int | float | str | datetime, - step: int, -) -> TimeInfo: - """Helper function for parsing start and end arguments.""" - n_time_steps = time_axis.NumberOfTimeSteps - file_start_datetime = time_axis.StartDateTime - file_start_sec = time_axis.StartTimeOffset - start_sec = file_start_sec - - timespan = 0 - if time_axis.TimeAxisType == 3: - timespan = time_axis.TimeStep * (n_time_steps - 1) - elif time_axis.TimeAxisType == 4: - timespan = time_axis.TimeSpan - else: - raise ValueError("TimeAxisType not supported") - - file_end_sec = start_sec + timespan - end_sec = file_end_sec - - start_step = 0 - if isinstance(start, int): - start_step = start - elif isinstance(start, float): - start_sec = start - elif isinstance(start, str): - parts = start.split(",") - start = parts[0] - if len(parts) == 2: - end = parts[1] - start = pd.to_datetime(start) - - if isinstance(start, datetime): - start_sec = (start - file_start_datetime).total_seconds() - - end_step = n_time_steps - if isinstance(end, int): - if end < 0: - end = end_step + end + 1 - end_step = end - elif isinstance(end, float): - end_sec = end - elif isinstance(end, str): - end = pd.to_datetime(end) - - if isinstance(end, datetime): - end_sec = (end - file_start_datetime).total_seconds() - - if start_step < 0: - raise ValueError( - f"start cannot be before start of file. start={start_step} is invalid" - ) - - if start_sec < file_start_sec: - raise ValueError( - f"start cannot be before start of file start={start_step} is invalid" + @staticmethod + def parse( + time_axis: TimeAxis, + start: int | float | str | datetime, + end: int | float | str | datetime, + step: int, + ) -> TimeInfo: + """Helper function for parsing start and end arguments.""" + n_time_steps = time_axis.NumberOfTimeSteps + file_start_datetime = time_axis.StartDateTime + file_start_sec = time_axis.StartTimeOffset + start_sec = file_start_sec + + timespan = 0 + if time_axis.TimeAxisType == 3: + timespan = time_axis.TimeStep * (n_time_steps - 1) + elif time_axis.TimeAxisType == 4: + timespan = time_axis.TimeSpan + else: + raise ValueError("TimeAxisType not supported") + + file_end_sec = start_sec + timespan + end_sec = file_end_sec + + start_step = 0 + if isinstance(start, int): + start_step = start + elif isinstance(start, float): + start_sec = start + elif isinstance(start, str): + parts = start.split(",") + start = parts[0] + if len(parts) == 2: + end = parts[1] + start = pd.to_datetime(start) + + if isinstance(start, datetime): + start_sec = (start - file_start_datetime).total_seconds() + + end_step = n_time_steps + if isinstance(end, int): + if end < 0: + end = end_step + end + 1 + end_step = end + elif isinstance(end, float): + end_sec = end + elif isinstance(end, str): + end = pd.to_datetime(end) + + if isinstance(end, datetime): + end_sec = (end - file_start_datetime).total_seconds() + + if start_step < 0: + raise ValueError( + f"start cannot be before start of file. start={start_step} is invalid" + ) + + if start_sec < file_start_sec: + raise ValueError( + f"start cannot be before start of file start={start_step} is invalid" + ) + + if (end_sec < start_sec) or (end_step < start_step): + raise ValueError("end must be after start") + + if end_step > n_time_steps: + raise ValueError( + f"end cannot be after end of file. end={end_step} is invalid." + ) + + if end_sec > file_end_sec: + raise ValueError( + f"end cannot be after end of file. end={end_sec} is invalid." + ) + + file_start_new = None + if time_axis.TimeAxisType == TimeAxisType.CalendarEquidistant: + dt = time_axis.TimeStep + if (start_sec > file_start_sec) and (start_step == 0): + # we can find the coresponding step + start_step = int((start_sec - file_start_sec) / dt) + file_start_new = file_start_datetime + timedelta(seconds=start_step * dt) + elif time_axis.TimeAxisType == TimeAxisType.CalendarNonEquidistant: + if start_sec > file_start_sec: + file_start_new = file_start_datetime + timedelta(seconds=start_sec) + + timestep = _parse_step(time_axis, step) + + return TimeInfo( + file_start_new, start_step, start_sec, end_step, end_sec, timestep ) - if (end_sec < start_sec) or (end_step < start_step): - raise ValueError("end must be after start") - - if end_step > n_time_steps: - raise ValueError(f"end cannot be after end of file. end={end_step} is invalid.") - - if end_sec > file_end_sec: - raise ValueError(f"end cannot be after end of file. end={end_sec} is invalid.") - - file_start_new = None - if time_axis.TimeAxisType == 3: - dt = time_axis.TimeStep - if (start_sec > file_start_sec) and (start_step == 0): - # we can find the coresponding step - start_step = int((start_sec - file_start_sec) / dt) - file_start_new = file_start_datetime + timedelta(seconds=start_step * dt) - elif time_axis.TimeAxisType == 4: - if start_sec > file_start_sec: - file_start_new = file_start_datetime + timedelta(seconds=start_sec) - - timestep = _parse_step(time_axis, step) - - return TimeInfo(file_start_new, start_step, start_sec, end_step, end_sec, timestep) - def _parse_step(time_axis: TimeAxis, step: int) -> float | None: """Helper function for parsing step argument.""" From 608cfa110f5ced3bd673b2fa49005d6b0b5be3ba Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Fri, 22 Nov 2024 08:07:54 +0100 Subject: [PATCH 3/3] Use enum insted of int --- mikeio/generic.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/mikeio/generic.py b/mikeio/generic.py index 8c8b9fa31..8d6777331 100644 --- a/mikeio/generic.py +++ b/mikeio/generic.py @@ -599,7 +599,7 @@ def extract( is_layered_dfsu = dfs_i.ItemInfo[0].Name == "Z coordinate" - time = TimeInfo.parse(dfs_i.FileInfo.TimeAxis, start, end, step) + time = _TimeInfo.parse(dfs_i.FileInfo.TimeAxis, start, end, step) item_numbers = _valid_item_numbers( dfs_i.ItemInfo, items, ignore_first=is_layered_dfsu ) @@ -647,7 +647,7 @@ def extract( @dataclass -class TimeInfo: +class _TimeInfo: """Parsed time information. Attributes @@ -680,7 +680,7 @@ def parse( start: int | float | str | datetime, end: int | float | str | datetime, step: int, - ) -> TimeInfo: + ) -> _TimeInfo: """Helper function for parsing start and end arguments.""" n_time_steps = time_axis.NumberOfTimeSteps file_start_datetime = time_axis.StartDateTime @@ -760,24 +760,24 @@ def parse( if start_sec > file_start_sec: file_start_new = file_start_datetime + timedelta(seconds=start_sec) - timestep = _parse_step(time_axis, step) + timestep = _TimeInfo._parse_step(time_axis, step) - return TimeInfo( + return _TimeInfo( file_start_new, start_step, start_sec, end_step, end_sec, timestep ) - -def _parse_step(time_axis: TimeAxis, step: int) -> float | None: - """Helper function for parsing step argument.""" - if step == 1: - timestep = None - elif time_axis.TimeAxisType == 3: - timestep = time_axis.TimeStep * step - elif time_axis.TimeAxisType == 4: - timestep = None - else: - raise ValueError("TimeAxisType not supported") - return timestep + @staticmethod + def _parse_step(time_axis: TimeAxis, step: int) -> float | None: + """Helper function for parsing step argument.""" + if step == 1: + timestep = None + elif time_axis.TimeAxisType == TimeAxisType.CalendarEquidistant: + timestep = time_axis.TimeStep * step + elif time_axis.TimeAxisType == TimeAxisType.CalendarNonEquidistant: + timestep = None + else: + raise ValueError("TimeAxisType not supported") + return timestep def avg_time(