summaryrefslogtreecommitdiff
path: root/date_calculator.py
diff options
context:
space:
mode:
authorKaufmann <Kau@avh.de>2025-04-02 11:16:56 +0200
committerKaufmann <Kau@avh.de>2025-04-02 11:16:56 +0200
commitd62c4b9b194ad6233107cf71a87870441b2b0d4b (patch)
treec9934e2f10022e69c38a639500b1fec64105cf8e /date_calculator.py
Vorstellung des Ausfallzeitenrechners im JF von Abt. 2.5.
Initiale Kritik: Kommentarfeld, speichern als PDF, Datumsformat
Diffstat (limited to 'date_calculator.py')
-rw-r--r--date_calculator.py154
1 files changed, 154 insertions, 0 deletions
diff --git a/date_calculator.py b/date_calculator.py
new file mode 100644
index 0000000..df13721
--- /dev/null
+++ b/date_calculator.py
@@ -0,0 +1,154 @@
+# 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)
+ truncated_end = min(end, prediction)
+ if truncated_start <= end and truncated_start <= prediction:
+ considered_periods.append((truncated_start, truncated_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 \ No newline at end of file