diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 9eb62c750a..4270d89a18 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -7,6 +7,7 @@ import webbrowser import ifcopenshell.api import ifcopenshell.util.date from datetime import datetime +from datetime import timedelta from dateutil import parser from blenderbim.bim.ifc import IfcStore from bpy_extras.io_utils import ImportHelper @@ -301,8 +302,7 @@ class LoadTaskProperties(bpy.types.Operator): task_time = Data.task_times[task["TaskTime"]] item.start = self.canonicalise_time(task_time["ScheduleStart"]) item.finish = self.canonicalise_time(task_time["ScheduleFinish"]) - # TODO: duration - item.duration = "-" + item.duration = str(task_time["ScheduleDuration"].days) if task_time["ScheduleDuration"] else "-" else: item.start = "-" item.finish = "-" @@ -431,6 +431,8 @@ class EnableEditingTaskTime(bpy.types.Operator): if data_type == "string": if isinstance(data[attribute.name()], datetime): new.string_value = "" if new.is_null else data[attribute.name()].isoformat() + elif isinstance(data[attribute.name()], timedelta): + new.string_value = "" if new.is_null else ifcopenshell.util.date.datetime2ifc(data[attribute.name()], "IfcDuration") else: new.string_value = "" if new.is_null else data[attribute.name()] elif data_type == "boolean": @@ -497,6 +499,8 @@ class EditTaskTime(bpy.types.Operator): attributes[key] = parser.parse(value, dayfirst=True, fuzzy=True) except: attributes[key] = None + elif key == "ScheduleDuration": + attributes[key] = ifcopenshell.util.date.ifc2datetime(value) return attributes diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py index a79eb12664..b83545d38e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py @@ -1,6 +1,4 @@ import ifcopenshell.util.date -from datetime import datetime -from datetime import timedelta class Usecase: def __init__(self, file, **settings): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py index 2df5feed7c..351b0aec83 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py @@ -154,7 +154,8 @@ class Data: continue if "Start" in key or "Finish" in key or key == "StatusTime": data[key] = ifcopenshell.util.date.ifc2datetime(value) - # TODO parse duration + elif key == "ScheduleDuration": + data[key] = ifcopenshell.util.date.ifc2datetime(value) cls.task_times[task_time.id()] = data @classmethod diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py index 5859e48221..0680a5ece2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py @@ -13,4 +13,7 @@ class Usecase: if "Start" in name or "Finish" in name or name == "StatusTime": if value: value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") + if name == "ScheduleDuration": + if value: + value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") setattr(self.settings["task_time"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/util/date.py b/src/ifcopenshell-python/ifcopenshell/util/date.py index 8d37e952ca..f75370d348 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/date.py +++ b/src/ifcopenshell-python/ifcopenshell/util/date.py @@ -1,6 +1,39 @@ import datetime from re import findall +# https://gist.github.com/spatialtime/c1924a3b178b4fe721fe406e0bf1a1dc +import re +from datetime import timedelta + + +def format_duration(td): + s = td.seconds + ms = td.microseconds + if ms != 0: # Round microseconds to milliseconds. + ms /= 1000000 + ms = round(ms,3) + s += ms + return "P{}DT{}S".format(td.days,s) + +def parse_duration(iso_duration): + m = re.match(r'^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:.\d+)?)S)?$', + iso_duration) + if m is None: + raise ValueError("invalid ISO 8601 duration string") + days = 0 + hours = 0 + minutes = 0 + seconds = 0.0 + if m[3]: + days = int(m[3]) + if m[4]: + hours = int(m[4]) + if m[5]: + minutes = int(m[5]) + if m[6]: + seconds = float(m[6]) + return timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds) + def duration2dict(duration): results = {} @@ -11,7 +44,7 @@ def duration2dict(duration): def ifc2datetime(element): if isinstance(element, str) and element[0] == "P": # IfcDuration - return duration2dict(element) + return parse_duration(element) elif isinstance(element, str) and element[2] == ":": # IfcTime return datetime.time.fromisoformat(element) elif isinstance(element, str) and ":" in element: # IfcDateTime @@ -39,8 +72,10 @@ def ifc2datetime(element): def datetime2ifc(dt, ifc_type): - if isinstance(dt, str): + if isinstance(dt, str) and ifc_type != "IfcDuration": dt = datetime.datetime.fromisoformat(dt) + if ifc_type == "IfcDuration": + return format_duration(dt) if ifc_type == "IfcTimeStamp": return int(dt.timestamp()) elif ifc_type == "IfcDateTime": diff --git a/src/ifcp6/ifcp6/p62ifc.py b/src/ifcp6/ifcp6/p62ifc.py index 23a29ca062..1a79f83f2e 100644 --- a/src/ifcp6/ifcp6/p62ifc.py +++ b/src/ifcp6/ifcp6/p62ifc.py @@ -1,4 +1,5 @@ import datetime +from datetime import timedelta import ifcopenshell import ifcopenshell.api import ifcopenshell.util.date @@ -121,6 +122,7 @@ class P62Ifc: "Identification": activity.find("pr:Id", self.ns).text, "StartDate": datetime.datetime.fromisoformat(activity.find("pr:StartDate", self.ns).text), "FinishDate": datetime.datetime.fromisoformat(activity.find("pr:FinishDate", self.ns).text), + "PlannedDuration": datetime.timedelta(days=float(activity.find("pr:PlannedDuration", self.ns).text)), "Status": activity.find("pr:Status", self.ns).text, "ifc": None, } @@ -335,6 +337,7 @@ class P62Ifc: attributes={ "ScheduleStart": activity["StartDate"], "ScheduleFinish": activity["FinishDate"], + "ScheduleDuration": activity["PlannedDuration"] if activity["PlannedDuration"] else None, }, )