2022-01-19 12:18:33 +11:00
|
|
|
# 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/>.
|
|
|
|
|
|
2021-04-29 17:52:45 +10:00
|
|
|
import datetime
|
2021-01-22 16:21:17 +11:00
|
|
|
from re import findall
|
2023-03-29 00:47:05 +00:00
|
|
|
from dateutil import parser
|
2021-01-22 16:21:17 +11:00
|
|
|
|
2021-05-04 12:52:14 +10:00
|
|
|
try:
|
|
|
|
|
import isodate
|
2024-05-11 15:14:39 +10:00
|
|
|
except ModuleNotFoundError as e:
|
|
|
|
|
print(f"Note: duration parsing not available due to missing dependencies: util.date - {e}")
|
2021-01-22 16:21:17 +11:00
|
|
|
|
|
|
|
|
|
2021-05-05 11:59:33 +10:00
|
|
|
def timedelta2duration(timedelta):
|
|
|
|
|
components = {
|
|
|
|
|
"days": getattr(timedelta, "days", 0),
|
|
|
|
|
"hours": 0,
|
|
|
|
|
"minutes": 0,
|
|
|
|
|
"seconds": getattr(timedelta, "seconds", 0),
|
|
|
|
|
}
|
|
|
|
|
if components["seconds"]:
|
|
|
|
|
components["hours"], components["minutes"], components["seconds"] = [
|
2023-09-24 13:14:44 +10:00
|
|
|
int(i) for i in str(datetime.timedelta(seconds=components["seconds"])).split(":")
|
2021-05-05 11:59:33 +10:00
|
|
|
]
|
|
|
|
|
return isodate.Duration(**components)
|
|
|
|
|
|
|
|
|
|
|
2021-01-22 16:21:17 +11:00
|
|
|
def ifc2datetime(element):
|
2021-05-10 20:54:41 +10:00
|
|
|
if isinstance(element, str) and "P" in element[0:2]: # IfcDuration
|
2023-05-03 16:09:27 +01:00
|
|
|
duration = parse_duration(element)
|
2021-05-04 12:52:14 +10:00
|
|
|
if isinstance(duration, datetime.timedelta):
|
2021-05-05 11:59:33 +10:00
|
|
|
return timedelta2duration(duration)
|
2021-05-04 12:52:14 +10:00
|
|
|
return duration
|
2021-06-17 13:23:09 +10:00
|
|
|
elif isinstance(element, str) and len(element) > 3 and element[2] == ":": # IfcTime
|
2021-04-29 17:52:45 +10:00
|
|
|
return datetime.time.fromisoformat(element)
|
|
|
|
|
elif isinstance(element, str) and ":" in element: # IfcDateTime
|
|
|
|
|
return datetime.datetime.fromisoformat(element)
|
|
|
|
|
elif isinstance(element, str): # IfcDate
|
|
|
|
|
return datetime.date.fromisoformat(element)
|
2021-01-22 16:21:17 +11:00
|
|
|
elif isinstance(element, int): # IfcTimeStamp
|
2021-04-29 17:52:45 +10:00
|
|
|
return datetime.datetime.fromtimestamp(element)
|
2021-01-22 16:21:17 +11:00
|
|
|
elif element.is_a("IfcDateAndTime"):
|
2021-04-29 17:52:45 +10:00
|
|
|
return datetime.datetime(
|
2021-01-22 16:21:17 +11:00
|
|
|
element.DateComponent.YearComponent,
|
|
|
|
|
element.DateComponent.MonthComponent,
|
|
|
|
|
element.DateComponent.DayComponent,
|
|
|
|
|
element.TimeComponent.HourComponent,
|
|
|
|
|
element.TimeComponent.MinuteComponent,
|
2021-07-22 13:22:23 +10:00
|
|
|
int(element.TimeComponent.SecondComponent),
|
2021-01-22 16:21:17 +11:00
|
|
|
# TODO: implement TimeComponent timezone
|
|
|
|
|
)
|
|
|
|
|
elif element.is_a("IfcCalendarDate"):
|
2021-04-29 17:52:45 +10:00
|
|
|
return datetime.date(
|
2021-01-22 16:21:17 +11:00
|
|
|
element.YearComponent,
|
|
|
|
|
element.MonthComponent,
|
|
|
|
|
element.DayComponent,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-05-03 16:09:27 +01:00
|
|
|
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):
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
final_string = ""
|
|
|
|
|
final_string += f"{years} y " if years else ""
|
|
|
|
|
final_string += f"{months} m " if months else ""
|
|
|
|
|
final_string += f"{weeks} w " if weeks else ""
|
|
|
|
|
final_string += f"{days} d " if days else ""
|
2023-08-24 13:43:25 +01:00
|
|
|
final_string += f"{round(float(hours),2)} h " if hours else ""
|
2023-05-03 16:09:27 +01:00
|
|
|
final_string += f"{minutes} m " if minutes else ""
|
|
|
|
|
final_string += f"{seconds} s " if seconds else ""
|
|
|
|
|
|
|
|
|
|
return final_string
|
|
|
|
|
|
|
|
|
|
|
2021-01-22 16:21:17 +11:00
|
|
|
def datetime2ifc(dt, ifc_type):
|
2021-05-04 12:52:14 +10:00
|
|
|
if isinstance(dt, str):
|
|
|
|
|
if ifc_type == "IfcDuration":
|
|
|
|
|
return dt
|
2023-02-02 23:01:49 +11:00
|
|
|
try:
|
|
|
|
|
dt = datetime.datetime.fromisoformat(dt)
|
|
|
|
|
except:
|
|
|
|
|
dt = datetime.time.fromisoformat(dt)
|
2021-05-04 12:52:14 +10:00
|
|
|
|
2021-05-04 00:27:53 +00:00
|
|
|
if ifc_type == "IfcDuration":
|
2021-05-04 12:52:14 +10:00
|
|
|
return isodate.duration_isoformat(dt)
|
|
|
|
|
elif ifc_type == "IfcTimeStamp":
|
2021-01-22 16:21:17 +11:00
|
|
|
return int(dt.timestamp())
|
|
|
|
|
elif ifc_type == "IfcDateTime":
|
2021-04-29 17:52:45 +10:00
|
|
|
if isinstance(dt, datetime.datetime):
|
|
|
|
|
return dt.isoformat()
|
|
|
|
|
elif isinstance(dt, datetime.date):
|
2023-09-24 13:14:44 +10:00
|
|
|
return datetime.datetime.combine(dt, datetime.datetime.min.time()).isoformat()
|
2021-01-22 16:21:17 +11:00
|
|
|
elif ifc_type == "IfcDate":
|
2021-04-29 17:52:45 +10:00
|
|
|
if isinstance(dt, datetime.datetime):
|
|
|
|
|
return dt.date().isoformat()
|
|
|
|
|
elif isinstance(dt, datetime.date):
|
|
|
|
|
return dt.isoformat()
|
2021-01-22 16:21:17 +11:00
|
|
|
elif ifc_type == "IfcTime":
|
2021-04-29 17:52:45 +10:00
|
|
|
if isinstance(dt, datetime.datetime):
|
|
|
|
|
return dt.time().isoformat()
|
|
|
|
|
elif isinstance(dt, datetime.time):
|
|
|
|
|
return dt.isoformat()
|
2021-02-17 21:12:04 +11:00
|
|
|
elif ifc_type == "IfcCalendarDate":
|
2023-05-03 16:09:27 +01:00
|
|
|
return {
|
|
|
|
|
"DayComponent": dt.day,
|
|
|
|
|
"MonthComponent": dt.month,
|
|
|
|
|
"YearComponent": dt.year,
|
|
|
|
|
}
|
2021-02-17 21:12:04 +11:00
|
|
|
elif ifc_type == "IfcLocalTime":
|
|
|
|
|
# TODO implement timezones
|
2023-05-03 16:09:27 +01:00
|
|
|
return {
|
|
|
|
|
"HourComponent": dt.hour,
|
|
|
|
|
"MinuteComponent": dt.minute,
|
|
|
|
|
"SecondComponent": dt.second,
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-29 00:47:05 +00:00
|
|
|
|
|
|
|
|
def string_to_date(string):
|
|
|
|
|
if not string:
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
return parser.isoparse(string)
|
|
|
|
|
except:
|
|
|
|
|
try:
|
|
|
|
|
return parser.parse(string, dayfirst=True, fuzzy=True)
|
|
|
|
|
except:
|
|
|
|
|
return None
|
|
|
|
|
|
2023-05-03 16:09:27 +01:00
|
|
|
|
2023-03-29 00:47:05 +00:00
|
|
|
def string_to_duration(duration_string):
|
|
|
|
|
# TODO support years, months, weeks aswell
|
|
|
|
|
days = 0
|
|
|
|
|
hours = 0
|
|
|
|
|
minutes = 0
|
|
|
|
|
seconds = 0
|
|
|
|
|
match = findall(r"(\d+\.?\d*)d", duration_string)
|
|
|
|
|
if match:
|
|
|
|
|
days = float(match[0])
|
|
|
|
|
match = findall(r"(\d+\.?\d*)h", duration_string)
|
|
|
|
|
if match:
|
|
|
|
|
hours = float(match[0])
|
|
|
|
|
match = findall(r"(\d+\.?\d*)m", duration_string)
|
|
|
|
|
if match:
|
|
|
|
|
minutes = float(match[0])
|
|
|
|
|
match = findall(r"(\d+\.?\d*)s", duration_string)
|
|
|
|
|
if match:
|
|
|
|
|
seconds = float(match[0])
|
2023-09-24 13:14:44 +10:00
|
|
|
return isodate.duration_isoformat(datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds))
|
2023-05-03 16:09:27 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_duration(value):
|
|
|
|
|
if not value:
|
|
|
|
|
return None
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
if "P" in value:
|
|
|
|
|
try:
|
|
|
|
|
return isodate.parse_duration(value)
|
|
|
|
|
except:
|
2023-09-24 13:14:44 +10:00
|
|
|
print("Error parsing ISO string duration")
|
|
|
|
|
return None
|
2023-05-03 16:09:27 +01:00
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
final_string = "P"
|
|
|
|
|
value_upper = value.upper()
|
|
|
|
|
for char in value_upper:
|
|
|
|
|
if char.isdigit():
|
|
|
|
|
final_string += char
|
|
|
|
|
elif char == "D":
|
|
|
|
|
final_string += "D"
|
2023-09-24 13:14:44 +10:00
|
|
|
if "H" in value_upper or "S" in value_upper or "MIN" in value_upper:
|
2023-05-03 16:09:27 +01:00
|
|
|
final_string += "T"
|
|
|
|
|
elif char == "W":
|
|
|
|
|
final_string += "W"
|
|
|
|
|
elif char == "M":
|
|
|
|
|
final_string += "M"
|
|
|
|
|
elif char == "Y":
|
|
|
|
|
final_string += "Y"
|
|
|
|
|
elif char == "H":
|
|
|
|
|
final_string = (
|
2023-09-24 13:14:44 +10:00
|
|
|
final_string[:1] + "T" + final_string[1:] if "T" not in final_string else final_string
|
2023-05-03 16:09:27 +01:00
|
|
|
)
|
|
|
|
|
final_string += "H"
|
|
|
|
|
elif char == "M":
|
|
|
|
|
if "MIN" in value_upper and "T" not in final_string:
|
|
|
|
|
final_string = final_string[:1] + "T" + final_string[1:]
|
|
|
|
|
final_string += "M"
|
|
|
|
|
elif char == "S":
|
|
|
|
|
final_string = (
|
2023-09-24 13:14:44 +10:00
|
|
|
final_string[:1] + "T" + final_string[1:] if "T" not in final_string else final_string
|
2023-05-03 16:09:27 +01:00
|
|
|
)
|
|
|
|
|
final_string += "S"
|
|
|
|
|
return isodate.parse_duration(final_string)
|
|
|
|
|
except:
|
|
|
|
|
print("error fuzzy parsing duration")
|
|
|
|
|
return None
|
2023-12-17 20:20:52 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def canonicalise_time(time):
|
|
|
|
|
if not time:
|
|
|
|
|
return "-"
|
|
|
|
|
return time.strftime("%d/%m/%y")
|