# date_calculator.py from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta import math class DateCalculator: @staticmethod def truncate_periods(periods, launch, prediction): considered_periods = [] for start, end, id in periods: # print(start) # print(launch) truncated_start = max(start, launch) considered_periods.append((truncated_start, end, id)) return considered_periods @staticmethod def round_periods(periods): rounded_periods = [] total_months = 0 for start, end, id in periods: year_diff = end.year - start.year month_diff = end.month - start.month months = year_diff * 12 + month_diff if end.day >= start.day: months += 1 rounded_end = start + relativedelta(months=months) - timedelta(days=1) rounded_periods.append((start, rounded_end, id)) total_months += months return rounded_periods, total_months @staticmethod def merge_periods(periods): if not periods: return [] periods.sort() merged_periods = [] current_start, current_end, current_id = periods[0] for start, end, id in periods[1:]: if start <= current_end + timedelta(days=1): current_end = max(current_end, end) else: merged_periods.append((current_start, current_end, current_id)) current_start, current_end, current_id = start, end, id merged_periods.append((current_start, current_end, current_id)) return merged_periods @staticmethod def find_non_overlapping_periods(existing_periods, test_period): existing_periods.sort(key=lambda x: x[0]) test_start, test_end, id = test_period non_overlapping_periods = [] for start, end, _ in existing_periods: if test_end < start: non_overlapping_periods.append((test_start, test_end, id)) return non_overlapping_periods elif test_start > end: continue else: if test_start < start: non_overlapping_periods.append((test_start, start - timedelta(days=1), id)) test_start = end + timedelta(days=1) if test_start <= test_end: non_overlapping_periods.append((test_start, test_end, id)) return non_overlapping_periods def calculate_prediction(self, launch_date, duration, **kwargs): total_days = 0 total_months = 0 updated = True prediction_start = launch_date + duration - timedelta(days = 1) prediction = prediction_start events = [] half_projects = [] full_projects = [] other_kwargs = {} for k, v in kwargs.items(): if k == "Sonstige": events.extend(v) elif k == "EZ 50%": half_projects.extend(v) elif k == "EZ 100%": full_projects.extend(v) elif k == "EZ pauschal": full_projects.extend(v) else: other_kwargs[k] = v while updated: updated = False considered_events = self.truncate_periods(events, launch_date, prediction) considered_full_projects = self.truncate_periods(full_projects, launch_date, prediction) considered_half_projects = self.truncate_periods(half_projects, launch_date, prediction) considered_full_projects_merged = self.merge_periods(considered_full_projects) considered_full_projects_rounded, months = self.round_periods(considered_full_projects_merged) considered_full_projects_merged2 = self.merge_periods(considered_full_projects_rounded) considered_full_projects_rounded2, months = self.round_periods(considered_full_projects_merged2) non_overlapping_half_projects = [] for test_interval in considered_half_projects: non_overlapping_half_projects.extend( self.find_non_overlapping_periods(considered_full_projects_rounded2, test_interval) ) considered_half_projects_merged = self.merge_periods(non_overlapping_half_projects) considered_half_projects_rounded, months2 = self.round_periods(considered_half_projects_merged) considered_half_projects_merged2 = self.merge_periods(considered_half_projects_rounded) considered_half_projects_rounded2, months2 = self.round_periods(considered_half_projects_merged2) all_projects_merged = self.merge_periods( considered_full_projects_rounded2 + considered_half_projects_rounded2 ) merged_event_periods = self.merge_periods(considered_events) non_overlapping_event_periods = [] for test_interval in merged_event_periods: non_overlapping_event_periods.extend( self.find_non_overlapping_periods(all_projects_merged, test_interval) ) new_total_months = months + math.ceil(months2 / 2) new_total_days = sum((end - start).days + 1 for start, end, _ in non_overlapping_event_periods) if new_total_days != total_days or new_total_months != total_months: total_days = new_total_days total_months = new_total_months updated = True prediction = launch_date + duration + relativedelta(months=total_months) + timedelta(days=total_days-1) # print(prediction, prediction_start + relativedelta(years = 6)) if prediction > prediction_start + relativedelta(years = 6): prediction = prediction_start + relativedelta(years = 6) return prediction, considered_full_projects_rounded2 + considered_half_projects_rounded2 + non_overlapping_event_periods