From 0f1d1b07973e94e893b51af38845317a757b60c9 Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Mon, 31 Aug 2026 16:58:36 +0200 Subject: [PATCH] Time/Date: accept different formats for date, YYYY-MM-DD [HH:MM[:SS]], DD-MM-YYYY [HH:MM[:SS]] and DD/MM/YYYY [HH:MM[:SS]], convert it to classic one used by pamhyr, and prevent user when he's using a wrong one --- src/View/BoundaryCondition/Edit/Table.py | 4 +- .../BoundaryConditionsAdisTS/Edit/Table.py | 4 +- .../Edit/Table.py | 4 +- src/View/LateralContribution/Edit/Table.py | 4 +- .../LateralContributionsAdisTS/Edit/Table.py | 4 +- src/View/Tools/PamhyrTable.py | 26 ++++++--- src/View/WeatherParameters/Edit/Table.py | 4 +- src/tools.py | 53 ++++++++++++++++++- 8 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index d36f56f5..d4e981df 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -24,7 +24,7 @@ from datetime import date, time, datetime, timedelta from tools import ( trace, timer, timestamp_to_old_pamhyr_date, - old_pamhyr_date_to_timestamp + old_pamhyr_date_to_timestamp, format_datetime ) from View.Tools.PamhyrTable import PamhyrTableModel @@ -97,7 +97,7 @@ class TableModel(PamhyrTableModel): if self._opt_data == "time": value = timestamp_to_old_pamhyr_date(int(v)) else: - value = str( + value = format_datetime( self._start_date + timedelta(seconds=float(v)) ) else: diff --git a/src/View/BoundaryConditionsAdisTS/Edit/Table.py b/src/View/BoundaryConditionsAdisTS/Edit/Table.py index 01179d92..e6c714d8 100644 --- a/src/View/BoundaryConditionsAdisTS/Edit/Table.py +++ b/src/View/BoundaryConditionsAdisTS/Edit/Table.py @@ -24,7 +24,7 @@ from datetime import date, time, datetime, timedelta from tools import ( trace, timer, timestamp_to_old_pamhyr_date, - old_pamhyr_date_to_timestamp + old_pamhyr_date_to_timestamp, format_datetime ) from View.Tools.PamhyrTable import PamhyrTableModel @@ -87,7 +87,7 @@ class TableModel(PamhyrTableModel): if self._opt_data == "time": value = timestamp_to_old_pamhyr_date(int(v)) else: - value = str( + value = format_datetime( self._start_date + timedelta(seconds=float(v)) ) else: diff --git a/src/View/BoundaryConditionsTemperature/Edit/Table.py b/src/View/BoundaryConditionsTemperature/Edit/Table.py index a29937b3..196d68d7 100644 --- a/src/View/BoundaryConditionsTemperature/Edit/Table.py +++ b/src/View/BoundaryConditionsTemperature/Edit/Table.py @@ -24,7 +24,7 @@ from datetime import date, time, datetime, timedelta from tools import ( trace, timer, timestamp_to_old_pamhyr_date, - old_pamhyr_date_to_timestamp + old_pamhyr_date_to_timestamp, format_datetime ) from View.Tools.PamhyrTable import PamhyrTableModel @@ -87,7 +87,7 @@ class TableModel(PamhyrTableModel): if self._opt_data == "time": value = timestamp_to_old_pamhyr_date(int(v)) else: - value = str( + value = format_datetime( self._start_date + timedelta(seconds=float(v)) ) else: diff --git a/src/View/LateralContribution/Edit/Table.py b/src/View/LateralContribution/Edit/Table.py index 330d2092..289b0265 100644 --- a/src/View/LateralContribution/Edit/Table.py +++ b/src/View/LateralContribution/Edit/Table.py @@ -23,7 +23,7 @@ from datetime import date, time, datetime, timedelta from tools import ( trace, timer, timestamp_to_old_pamhyr_date, - old_pamhyr_date_to_timestamp + old_pamhyr_date_to_timestamp, format_datetime ) from View.Tools.PamhyrTable import PamhyrTableModel @@ -76,7 +76,7 @@ class TableModel(PamhyrTableModel): if self._opt_data == "time": value = timestamp_to_old_pamhyr_date(int(v)) else: - value = str( + value = format_datetime( self._start_date + timedelta(seconds=float(v)) ) else: diff --git a/src/View/LateralContributionsAdisTS/Edit/Table.py b/src/View/LateralContributionsAdisTS/Edit/Table.py index 51d2540b..eacd21b1 100644 --- a/src/View/LateralContributionsAdisTS/Edit/Table.py +++ b/src/View/LateralContributionsAdisTS/Edit/Table.py @@ -24,7 +24,7 @@ from tools import ( trace, timer, timestamp_to_old_pamhyr_date, old_pamhyr_date_to_timestamp, - timestamp_to_old_pamhyr_date_adists + timestamp_to_old_pamhyr_date_adists, format_datetime ) from View.Tools.PamhyrTable import PamhyrTableModel @@ -90,7 +90,7 @@ class TableModel(PamhyrTableModel): if self._opt_data == "time": value = timestamp_to_old_pamhyr_date_adists(int(v)) else: - value = str( + value = format_datetime( self._start_date + timedelta(seconds=float(v)) ) else: diff --git a/src/View/Tools/PamhyrTable.py b/src/View/Tools/PamhyrTable.py index 9748a13d..a3196f97 100644 --- a/src/View/Tools/PamhyrTable.py +++ b/src/View/Tools/PamhyrTable.py @@ -19,9 +19,7 @@ import logging import traceback -from datetime import datetime - -from tools import trace, timer +from tools import trace, timer, parse_datetime from Model.Except import NotImplementedMethodeError @@ -36,8 +34,11 @@ from PyQt5.QtWidgets import ( QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, QComboBox, QStyledItemDelegate, QHeaderView, + QMessageBox, ) +_translate = QCoreApplication.translate + logger = logging.getLogger() @@ -63,9 +64,22 @@ class PamhyrTextDelegate(QStyledItemDelegate): class PamhyrTableModel(QAbstractTableModel): def date_to_elapsed_seconds(self, value): """Convert an entered date to seconds since the study start date.""" - entered_date = value - if not isinstance(entered_date, datetime): - entered_date = datetime.fromisoformat(str(value).strip()) + try: + entered_date = parse_datetime(value) + except ValueError as error: + QMessageBox.warning( + self._table_view, + _translate("PamhyrTableModel", "Invalid date/time"), + _translate( + "PamhyrTableModel", + "The entered date/time is invalid.\n" + "Expected formats:\n" + "YYYY-MM-DD [HH:MM[:SS]],\n" + "DD-MM-YYYY [HH:MM[:SS]] or\n" + "DD/MM/YYYY [HH:MM[:SS]]." + ) + ) + raise error return (entered_date - self._start_date).total_seconds() diff --git a/src/View/WeatherParameters/Edit/Table.py b/src/View/WeatherParameters/Edit/Table.py index 59191551..163dcfe4 100644 --- a/src/View/WeatherParameters/Edit/Table.py +++ b/src/View/WeatherParameters/Edit/Table.py @@ -23,7 +23,7 @@ from datetime import date, time, datetime, timedelta from tools import ( trace, timer, timestamp_to_old_pamhyr_date, - old_pamhyr_date_to_timestamp + old_pamhyr_date_to_timestamp, format_datetime ) from View.Tools.PamhyrTable import PamhyrTableModel @@ -77,7 +77,7 @@ class TableModel(PamhyrTableModel): if self._opt_data == "time": value = timestamp_to_old_pamhyr_date(int(v)) else: - value = str( + value = format_datetime( self._start_date + timedelta(seconds=float(v)) ) else: diff --git a/src/tools.py b/src/tools.py index 35f806fd..6422f808 100644 --- a/src/tools.py +++ b/src/tools.py @@ -22,7 +22,7 @@ import logging import sqlite3 import traceback -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from pathlib import Path from colorama import Fore @@ -239,6 +239,57 @@ def timestamp(dt: datetime): return dt.timestamp() +SUPPORTED_DATETIME_FORMATS = ( + "%d-%m-%Y %H:%M:%S.%f", + "%d-%m-%Y %H:%M:%S", + "%d-%m-%Y %H:%M", + "%d-%m-%Y", + "%d/%m/%Y %H:%M:%S.%f", + "%d/%m/%Y %H:%M:%S", + "%d/%m/%Y %H:%M", + "%d/%m/%Y", + "%d-%m-%y %H:%M:%S", + "%d/%m/%y %H:%M:%S", +) + + +def parse_datetime(value): + """Parse a supported date representation into a naive UTC datetime.""" + if isinstance(value, datetime): + parsed = value + else: + text = str(value).strip() + if text.endswith(("Z", "z")): + text = text[:-1] + "+00:00" + + try: + parsed = datetime.fromisoformat(text) + except ValueError as iso_error: + for date_format in SUPPORTED_DATETIME_FORMATS: + try: + parsed = datetime.strptime(text, date_format) + break + except ValueError: + continue + else: + raise ValueError( + f"Unsupported date format: {value!r}. Expected an ISO " + "date/time or one of: " + + ", ".join(SUPPORTED_DATETIME_FORMATS) + ) from iso_error + + # If the parsed datetime has timezone information, convert it to UTC and remove the timezone info + if parsed.tzinfo is not None: + parsed = parsed.astimezone(timezone.utc).replace(tzinfo=None) + + return parsed + + +def format_datetime(value): + """Return a datetime using the canonical application format.""" + return parse_datetime(value).strftime("%Y-%m-%d %H:%M:%S") + + def date_iso_to_timestamp(date: str): if type(date) is str: return timestamp(datetime.fromisoformat(date))