From 86ae5134173323deb6c025a4e36309818266527b Mon Sep 17 00:00:00 2001 From: eriks Date: Mon, 2 Dec 2024 17:59:09 +0200 Subject: [PATCH] Add main.py --- main.py | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..47565bb --- /dev/null +++ b/main.py @@ -0,0 +1,175 @@ +import calendar +import math +from collections import deque +from dataclasses import dataclass +from datetime import date, timedelta + +TRANSFERABLE_NATIONAL_HOLIDAYS = [ + (5, 4), # Restoration of Independence Day + (11, 18), # Proclamation Day of the Republic of Latvia +] + +NATIONAL_HOLIDAYS = [ + (1, 1), # New Year's Day + (5, 1), # Labour Day + (6, 23), # Midsummer's Eve + (6, 24), # Midsummer's Day + (12, 24), # Christmas Eve + (12, 25), # Christmas Day + (12, 26), # Second Day of Christmas + (12, 31), # New Year's Eve +] + +VARIABLE_DATE_HOLIDAYS = [] +TIME_FRAME = ( + date(2023, 7, 1), # (date.today() - timedelta(days=365)).replace(day=1), + (date.today() + timedelta(days=365 * 2 + 31)).replace(day=1), +) + + +@dataclass +class MonthHolidayData: + work_hours: int + work_days: int + + +@dataclass +class HolidayCalendarForYear: + holidays: list[date] + months: dict[int, MonthHolidayData] + + +def get_gauss_easter_sunday(year: int) -> date: + # https://www.geeksforgeeks.org/how-to-calculate-the-easter-date-for-a-given-year-using-gauss-algorithm/ + # All calculations done on the basis of Gauss Easter Algorithm + p = math.floor(year / 100) + d = (19 * (year % 19) + (15 - math.floor((13 + 8 * p) / 25) + p - p // 4) % 30) % 30 + e = (2 * (year % 4) + 4 * (year % 7) + 6 * d + (4 + p - p // 4) % 7) % 7 + days = (22 + d + e) + + if (d == 29) and (e == 6): # A corner case, when D is 29 + return date(year, 4, 19) + if (d == 28) and (e == 6): # Another corner case, when D is 28 + return date(year, 4, 18) + if days > 31: # If days > 31, move to the April, eg, 4th Month + return date(year, 4, days - 31) + return date(year, 3, days) # Otherwise, stay on March, March = 3rd Month + + +def get_mothers_day(year) -> date: + # The 8th is the lowest second day in the month + min_day = date(year, 5, 8) + # What day of the week is the 8th? + week_day = min_day.weekday() + # Sunday is weekday 6 + if week_day != 6: + # Replace just the day (of month) + min_day = min_day.replace(day=(8 + (6 - week_day) % 7)) + return min_day + + +def get_holidays_in_timeframe(start_date: date, end_date: date) -> list[date]: + holidays = [] + for year in range(start_date.year, end_date.year + 1): + for holiday in TRANSFERABLE_NATIONAL_HOLIDAYS: + holiday_date = date(year, *holiday) + holidays.append(holiday_date) + if holiday_date.weekday() == 5: + holidays.append(holiday_date + timedelta(days=2)) + elif holiday_date.weekday() == 6: + holidays.append(holiday_date + timedelta(days=1)) + holidays += [date(year, *holiday) for holiday in NATIONAL_HOLIDAYS] + easter_sunday = get_gauss_easter_sunday(year) + holidays += [ + easter_sunday - timedelta(days=2), + easter_sunday, + easter_sunday + timedelta(days=1), + easter_sunday + timedelta(days=49), # Pentecost + get_mothers_day(year), + ] + return sorted(holidays) + + +def get_holiday_calendar_for_timeframe(start_date: date, end_date: date) -> dict[int, HolidayCalendarForYear]: + holidays_by_year = {} + for holiday in get_holidays_in_timeframe(start_date, end_date): + if holiday.year not in holidays_by_year: + holidays_by_year[holiday.year] = [] + holidays_by_year[holiday.year].append(holiday) + + holiday_calendar = {} + for year in range(start_date.year, end_date.year + 1): + start_month = start_date.month if start_date.year == year else 1 + end_month = end_date.month if end_date.year == year else 13 + month_holidays = sorted(h for h in holidays_by_year[year] if start_date <= h <= end_date) + holiday_calendar[year] = HolidayCalendarForYear(holidays=month_holidays, months={}) + for month in range(start_month, end_month): + work_days = work_hours = 0 + for day in calendar.Calendar().itermonthdates(year, month): + if not day.month == month: + continue + if day.weekday() < 5 and day not in month_holidays: + work_hours += 7 if day + timedelta(days=1) in month_holidays else 8 + work_days += 1 + holiday_calendar[year].months[month] = MonthHolidayData(work_days=work_days, work_hours=work_hours) + return holiday_calendar + + +def print_working_day_calendar_for_time_frame(holiday_calendar: dict[int, HolidayCalendarForYear]) -> None: + print("Month,Work Days,Work Hours") + for year in sorted(holiday_calendar): + for month in sorted(holiday_calendar[year].months): + data = holiday_calendar[year].months[month] + print(f"{year}-{month:02}-01,{data.work_days},{data.work_hours}") + print("- - - - - - - - - - - - - - -") + + +def main(salary: int, holiday_days: int = 5): + start_date, end_date = TIME_FRAME + # start_date = (date.today() - timedelta(days=400)).replace(day=1) + # end_date = (date.today() + timedelta(days=365*2 + 31)).replace(day=1) + + holiday_calendar = get_holiday_calendar_for_timeframe(*TIME_FRAME) + print_working_day_calendar_for_time_frame(holiday_calendar) + + # List of per month holiday salary coefficient and work day coefficient, + # e.g., [((2024, 12) 0.848, 0.055)] + results: list[tuple[date, float, float]] = [] + + # Queue consists of last 6 month pay per day coefficients ( 1 ÷ + queue: deque[float] = deque(maxlen=6) + # queue: list[float] = [] + for year in range(start_date.year, end_date.year + 1): + start_month = start_date.month if start_date.year == year else 1 + end_month = end_date.month if end_date.year == year else 13 + for month in range(start_month, end_month): + queue.append(1 / holiday_calendar[year].months[month].work_days) + # if len(queue) > 6: + # queue.pop(0) + if not len(queue) == 6: + continue + y, m = (year + 1, 1) if month == 12 else (year, month + 1) + d = date(y, m, 1) + if d < end_date: + results.append( + ( + d, + (sum(queue) / 6), + (1 / holiday_calendar[y].months[m].work_days), + ) + ) + # sorted(results, key=lambda t: t[1] - t[2]) + print("Year - Month - Hol €/d - Wrk €/d => Hol d diff (Holiday salary diff) | coeff.") + for month, hol_coeff, wrk_cof in sorted(results, key=lambda t: (t[0])): + # for month, hol_coeff, wrk_cof in sorted(results, key=lambda t: (t[1]-t[2], t[0])): + # for month, hol_coeff, wrk_cof in sorted(results, key=lambda t: (holiday_calendar[t[0].year].months[t[0].month].work_days, t[0])): + print( + f"{month.year} - {month.month:5} - {hol_coeff:7.4f} - {wrk_cof:7.4f} => " + f"{(hol_coeff - wrk_cof):7.4f} ({(hol_coeff - wrk_cof) * holiday_days:7.04f}) | " + f"{hol_coeff / wrk_cof :6.4f} ||" + f" {hol_coeff * holiday_days + wrk_cof * (holiday_calendar[month.year].months[month.month].work_days - holiday_days):7.4f} => {(hol_coeff * holiday_days + wrk_cof * (holiday_calendar[month.year].months[month.month].work_days - holiday_days))*salary:7.4f} " + ) + + +if __name__ == "__main__": + main(1700, 10)