fix offset date calculation when finish date lies outside of calendar range

This commit is contained in:
Sigma Dimensions
2022-12-18 03:34:20 +01:00
parent 30dc13f1a7
commit ea1c306733
@@ -25,7 +25,8 @@ def derive_calendar(task):
calendar = [ calendar = [
rel.RelatingControl rel.RelatingControl
for rel in task.HasAssignments or [] for rel in task.HasAssignments or []
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar") if rel.is_a("IfcRelAssignsToControl")
and rel.RelatingControl.is_a("IfcWorkCalendar")
] ]
if calendar: if calendar:
return calendar[0] return calendar[0]
@@ -38,7 +39,11 @@ def count_working_days(start, finish, calendar):
current_date = datetime.date(start.year, start.month, start.day) current_date = datetime.date(start.year, start.month, start.day)
finish_date = datetime.date(finish.year, finish.month, finish.day) finish_date = datetime.date(finish.year, finish.month, finish.day)
while current_date < finish_date: while current_date < finish_date:
if calendar and calendar.WorkingTimes and is_working_day(current_date, calendar): if (
calendar
and calendar.WorkingTimes
and is_working_day(current_date, calendar)
):
result += 1 result += 1
elif not calendar: elif not calendar:
result += 1 result += 1
@@ -46,7 +51,9 @@ def count_working_days(start, finish, calendar):
return result return result
def get_start_or_finish_date(start, duration, duration_type, calendar, date_type="FINISH"): def get_start_or_finish_date(
start, duration, duration_type, calendar, date_type="FINISH"
):
if not duration.days: if not duration.days:
# Typically a milestone will have zero duration, so the start == finish # Typically a milestone will have zero duration, so the start == finish
return start return start
@@ -65,12 +72,13 @@ def offset_date(start, duration, duration_type, calendar):
abs_duration = abs(duration.days) abs_duration = abs(duration.days)
date_offset = datetime.timedelta(days=1 if duration.days > 0 else -1) date_offset = datetime.timedelta(days=1 if duration.days > 0 else -1)
while abs_duration > 0: while abs_duration > 0:
if duration_type == "ELAPSEDTIME" or not calendar or not calendar.WorkingTimes: if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(
current_date, calendar
):
abs_duration -= 1 abs_duration -= 1
elif ifcopenshell.util.sequence.is_working_day(current_date, calendar): elif is_working_day(current_date, calendar):
abs_duration -= 1 abs_duration -= 1
current_date += date_offset current_date += date_offset
if duration.days > 0: if duration.days > 0:
current_date = get_soonest_working_day(current_date, duration_type, calendar) current_date = get_soonest_working_day(current_date, duration_type, calendar)
else: else:
@@ -79,17 +87,21 @@ def offset_date(start, duration, duration_type, calendar):
def get_soonest_working_day(start, duration_type, calendar): def get_soonest_working_day(start, duration_type, calendar):
if duration_type == "ELAPSEDTIME" or not calendar or not calendar.WorkingTimes: if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(start, calendar):
return start return start
while not is_working_day(start, calendar): while not is_working_day(start, calendar):
if not is_calendar_applicable(start, calendar):
break
start += datetime.timedelta(days=1) start += datetime.timedelta(days=1)
return start return start
def get_recent_working_day(start, duration_type, calendar): def get_recent_working_day(start, duration_type, calendar):
if duration_type == "ELAPSEDTIME" or not calendar or not calendar.WorkingTimes: if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(start, calendar):
return start return start
while not is_working_day(start, calendar): while not is_working_day(start, calendar):
if not is_calendar_applicable(start, calendar):
break
start -= datetime.timedelta(days=1) start -= datetime.timedelta(days=1)
return start return start
@@ -110,27 +122,46 @@ def is_working_day(day, calendar):
return is_working_day return is_working_day
def is_work_time_applicable_to_day(work_time, day): @lru_cache(maxsize=None)
start = None def is_calendar_applicable(day, calendar):
finish = None if not calendar or not calendar.WorkingTimes:
return False
is_applicable = False
for work_time in calendar.WorkingTimes or []:
if is_day_in_work_time(day, work_time):
is_applicable = True
break
return is_applicable
def is_day_in_work_time(day, work_time):
is_day_in_work_time = False
if isinstance(day, datetime.datetime): if isinstance(day, datetime.datetime):
day = datetime.date(day.year, day.month, day.day) day = datetime.date(day.year, day.month, day.day)
if work_time.Start: if work_time.Start:
start = ifcopenshell.util.date.ifc2datetime(work_time.Start) start = ifcopenshell.util.date.ifc2datetime(work_time.Start)
if start > day: if day > start:
return False is_day_in_work_time = True
else:
is_day_in_work_time = False
if work_time.Finish: if work_time.Finish:
finish = ifcopenshell.util.date.ifc2datetime(work_time.Finish) finish = ifcopenshell.util.date.ifc2datetime(work_time.Finish)
if finish < day: if day < finish:
return False is_day_in_work_time = True
else:
is_day_in_work_time = False
return is_day_in_work_time
def is_work_time_applicable_to_day(work_time, day):
if not is_day_in_work_time(day, work_time):
return False
if not work_time.RecurrencePattern: if not work_time.RecurrencePattern:
return True return True
if isinstance(day, datetime.datetime):
day = datetime.date(day.year, day.month, day.day)
recurrence = work_time.RecurrencePattern recurrence = work_time.RecurrencePattern
if recurrence.RecurrenceType == "DAILY": if recurrence.RecurrenceType == "DAILY":
if not recurrence.Interval and not recurrence.Occurrences: if not recurrence.Interval and not recurrence.Occurrences:
return True return True
@@ -149,13 +180,16 @@ def is_work_time_applicable_to_day(work_time, day):
return False # TODO return False # TODO
elif recurrence.RecurrenceType == "MONTHLY_BY_POSITION": elif recurrence.RecurrenceType == "MONTHLY_BY_POSITION":
if not recurrence.Interval and not recurrence.Occurrences: if not recurrence.Interval and not recurrence.Occurrences:
return (day.weekday() + 1) in recurrence.WeekdayComponent and math.floor(day.day / 7) + 1 == recurrence[ return (day.weekday() + 1) in recurrence.WeekdayComponent and math.floor(
"Position" day.day / 7
] ) + 1 == recurrence["Position"]
return False # TODO return False # TODO
elif recurrence.RecurrenceType == "YEARLY_BY_DAY_OF_MONTH": elif recurrence.RecurrenceType == "YEARLY_BY_DAY_OF_MONTH":
if not recurrence.Interval and not recurrence.Occurrences: if not recurrence.Interval and not recurrence.Occurrences:
return day.month in recurrence.MonthComponent and day.day in recurrence.DayComponent return (
day.month in recurrence.MonthComponent
and day.day in recurrence.DayComponent
)
return False # TODO return False # TODO
elif recurrence.RecurrenceType == "YEARLY_BY_POSITION": elif recurrence.RecurrenceType == "YEARLY_BY_POSITION":
if not recurrence.Interval and not recurrence.Occurrences: if not recurrence.Interval and not recurrence.Occurrences: