Use isodate to parse iso durations #2783

Related #6585

Co-Authored-By: sboddy <12862430+sboddy@users.noreply.github.com>
This commit is contained in:
Andrej730
2025-04-21 12:35:00 +05:00
parent 311f9b96e7
commit f8ad05cb54
2 changed files with 47 additions and 27 deletions
@@ -17,15 +17,12 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import datetime import datetime
import isodate
import isodate.duration
from re import findall from re import findall
from dateutil import parser from dateutil import parser
from typing import Literal, Union, Any, overload 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): def timedelta2duration(timedelta):
components = { components = {
@@ -73,34 +70,30 @@ def ifc2datetime(element):
) )
def get_isosplit(s, split): def readable_ifc_duration(string: str) -> str:
if split in s: """Convert ISO duration to more readable string format.
n, s = s.split(split)
else:
n = 0
return n, s
Examples:
def readable_ifc_duration(string): - "P2Y3M1W4DT5H45M30S" -> "2 y 3 m 1 w 4 d 5 h 45 m 30 s"
string = string.split("P")[-1] - "P2Y3MT30S" -> "2 y 3 m 30 s"
"""
years, string = get_isosplit(string, "Y") duration = isodate.parse_duration(string, as_timedelta_if_possible=False)
months, string = get_isosplit(string, "M") assert isinstance(duration, isodate.duration.Duration)
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")
final_string = "" final_string = ""
final_string += f"{years} y " if years else "" final_string += f"{duration.years} y " if duration.years else ""
final_string += f"{months} m " if months 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"{weeks} w " if weeks else ""
final_string += f"{days} d " if days 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"{minutes} m " if minutes else ""
final_string += f"{seconds} s " if seconds else "" final_string += f"{seconds} s" if seconds else ""
return final_string return final_string
@@ -0,0 +1,27 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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"