Full Payroll Script
import calendar
from datetime import date,datetime, timedelta
'''
takes a date input yyyy-mm-dd and returs the date string
of the end of the month
'''
def get_end_of_month_iso(iso_string: str) -> str:
# Parse the incoming ISO string into a datetime object
dt = datetime.fromisoformat(iso_string)
# Get the last day number of the given month and year
_, last_day = calendar.monthrange(dt.year, dt.month)
# Replace the day component and preserve time/timezone metadata if present
end_of_month_dt = dt.replace(day=last_day)
# Return the result back as an ISO format string
return end_of_month_dt.isoformat().split('T')[0]
def is_weekend(iso_string: str) -> bool:
# Parse the ISO 8601 string into a datetime object
dt = datetime.fromisoformat(iso_string)
# .weekday() returns 0 for Monday ... 5 for Saturday, 6 for Sunday
return dt.weekday() >= 5
'''
takes two dates in yyyy-mm-dd format and returns all the dates between the two
dates including the two inputted dates
'''
def get_dates_between(start_iso: str, end_iso: str) -> list[str]:
# Parse ISO strings into datetime objects
start_dt = datetime.fromisoformat(start_iso).date()
end_dt = datetime.fromisoformat(end_iso).date()
# Calculate total days between start and end (inclusive)
total_days = (end_dt - start_dt).days + 1
# Generate list of ISO formatted strings
return [
(start_dt + timedelta(days=x)).isoformat()
for x in range(total_days)
]
'''
given an inputted date (in ISO date format: yyyy-mm-dd)
if the inputted date is on a weekend, return the first weekday before
'''
def get_nearest_past_weekday(iso_date_str: str) -> str:
# Parse the ISO string into a datetime object
dt = datetime.fromisoformat(iso_date_str)
# .weekday() returns 0 for Monday ... 5 for Saturday, 6 for Sunday
day_of_week = dt.weekday()
if day_of_week == 5: # Saturday
dt -= timedelta(days=1) # Move back to Friday
elif day_of_week == 6: # Sunday
dt -= timedelta(days=2) # Move back to Friday
return dt.isoformat().split('T')[0]
'''
given an inputted date (in ISO date format: yyyy-mm-dd)
if the inputted date is on a weekend, return the first weekday after
'''
def get_nearest_next_weekday(iso_date_str: str) -> str:
# Parse the ISO string into a datetime object
dt = datetime.fromisoformat(iso_date_str)
# .weekday() returns 0 for Monday ... 5 for Saturday, 6 for Sunday
day_of_week = dt.weekday()
if day_of_week == 5: # Saturday
dt += timedelta(days=2) # Move back to Friday
elif day_of_week == 6: # Sunday
dt += timedelta(days=1) # Move back to Friday
return dt.isoformat().split('T')[0]
'''
takes a set of paydates, and a list of employees and calculates a set of payments
employees are give as an array of dictionaries such as
employees = [
{'id':'1', 'start_date':'2026-01-01', 'salary': 100000},
{'id':'2', 'start_date':'2010-01-01', 'end_date':'2025-07-13', 'salary':70000},
{'id':'2', 'start_date':'2025-07-14', 'salary':90000}
]
each employee needs an id, a start_date and a salary
end_date is optional
for each date in the list of dates, a cash flow is generated with the salary given on the employee record \
multiplied by the factor passed in
the payment is adjusted proportionally if the employee start_date is between the current date and the last date,
or if the employees end_date is between the current date and the next date
'''
def payroll(dates, employees, factor=1.0):
employee_map = {}
for employee in employees:
if employee['id'] not in employee_map: employee_map[employee['id']] = []
employee_map[employee['id']].append(employee)
pass
results = []
last_date = None
for date in dates:
for employee in employees:
if employee['start_date']<=date:
if 'end_date' not in employee or not employee['end_date'] employee['start_date'] : start_date = last_date
if 'end_date' in employee and employee['end_date']< date:end_date = employee['end_date']
d1 = datetime.fromisoformat(start_date)
d2 = datetime.fromisoformat(end_date)
# Subtract the dates to get a timedelta object and extract .days
day_difference1 = abs((d2 - d1).days)
day_difference2 = abs((datetime.fromisoformat(date) - datetime.fromisoformat(last_date)).days)
payment = employee['salary']
payment = payment * factor * (day_difference1/day_difference2)
results.append({'date':date, 'id':employee['id'], 'payment':payment})
pass
pass
pass
last_date = date
pass
return results
Example Script
import payroll as py
'''
is a function that filters a set of dates for the pay dates.
the pay dates specified by this function are the 15th and end of month.
if the pay lands on a weekend, the first weekday before is chosen
'''
def pay_dates(dates):
results = []
for index,date in enumerate(dates):
split = date.split('-')
#if index == 0: results.append(date)
if date == py.get_end_of_month_iso(date) :results.append(py.get_nearest_past_weekday(date))
elif split[2] == '15': results.append(py.get_nearest_past_weekday(date))
return results
employees = [
{'id':'1', 'start_date':'2026-01-01', 'salary': 100000},
{'id':'2', 'start_date':'2010-01-01', 'end_date':'2025-07-13', 'salary':70000},
{'id':'2', 'start_date':'2025-07-14', 'salary':90000}
]
paydays = [x for x in pay_dates(py.get_dates_between('2025-01-01', '2027-03-12')) if x is not None]
#forecast the payroll cash flows
cash_flows = py.payroll(paydays, employees, factor = 0.5/12)
pass