From f8ad05cb54f2aba9bbc80687b6cf9569e8d0d407 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 21 Apr 2025 12:35:00 +0500 Subject: [PATCH] Use isodate to parse iso durations #2783 Related #6585 Co-Authored-By: sboddy <12862430+sboddy@users.noreply.github.com> --- .../ifcopenshell/util/date.py | 47 ++++++++----------- .../test/util/test_date.py | 27 +++++++++++ 2 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 src/ifcopenshell-python/test/util/test_date.py diff --git a/src/ifcopenshell-python/ifcopenshell/util/date.py b/src/ifcopenshell-python/ifcopenshell/util/date.py index d44a3e999e..583bf3bf1e 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/date.py +++ b/src/ifcopenshell-python/ifcopenshell/util/date.py @@ -17,15 +17,12 @@ # along with IfcOpenShell. If not, see . import datetime +import isodate +import isodate.duration from re import findall from dateutil import parser from typing import Literal, Union, Any, overload -try: - import isodate -except ModuleNotFoundError as e: - print(f"Note: duration parsing not available due to missing dependencies: util.date - {e}") - def timedelta2duration(timedelta): components = { @@ -73,34 +70,30 @@ def ifc2datetime(element): ) -def get_isosplit(s, split): - if split in s: - n, s = s.split(split) - else: - n = 0 - return n, s +def readable_ifc_duration(string: str) -> str: + """Convert ISO duration to more readable string format. - -def readable_ifc_duration(string): - string = string.split("P")[-1] - - years, string = get_isosplit(string, "Y") - months, string = get_isosplit(string, "M") - weeks, string = get_isosplit(string, "W") - days, string = get_isosplit(string, "D") - _, string = get_isosplit(string, "T") - hours, string = get_isosplit(string, "H") - minutes, string = get_isosplit(string, "M") - seconds, string = get_isosplit(string, "S") + Examples: + - "P2Y3M1W4DT5H45M30S" -> "2 y 3 m 1 w 4 d 5 h 45 m 30 s" + - "P2Y3MT30S" -> "2 y 3 m 30 s" + """ + duration = isodate.parse_duration(string, as_timedelta_if_possible=False) + assert isinstance(duration, isodate.duration.Duration) final_string = "" - final_string += f"{years} y " if years else "" - final_string += f"{months} m " if months else "" + final_string += f"{duration.years} y " if duration.years else "" + final_string += f"{duration.months} m " if duration.months else "" + + # Duration stores all other components as timedelta. + tdelta = duration.tdelta + weeks, days = divmod(tdelta.days, 7) final_string += f"{weeks} w " if weeks else "" final_string += f"{days} d " if days else "" - final_string += f"{round(float(hours),2)} h " if hours else "" + hours, seconds = divmod(tdelta.seconds, 3600) + minutes, seconds = divmod(seconds, 60) + final_string += f"{hours} h " if hours else "" final_string += f"{minutes} m " if minutes else "" - final_string += f"{seconds} s " if seconds else "" + final_string += f"{seconds} s" if seconds else "" return final_string diff --git a/src/ifcopenshell-python/test/util/test_date.py b/src/ifcopenshell-python/test/util/test_date.py new file mode 100644 index 0000000000..2f8cce1961 --- /dev/null +++ b/src/ifcopenshell-python/test/util/test_date.py @@ -0,0 +1,27 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import pytest +import test.bootstrap +import ifcopenshell.util.date as subject + + +class TestIsoDuration: + def test_run(self): + assert subject.readable_ifc_duration("P2Y3M1W4DT5H45M30S") == "2 y 3 m 1 w 4 d 5 h 45 m 30 s" + assert subject.readable_ifc_duration("P2Y3MT30S") == "2 y 3 m 30 s"