Forecasting Payroll
Payroll is often one of the largest expenses faced by a company. It is also mostly contractually defined. This page describes simple ways to forecast the payroll.- Pay Dates - one must determine what the pay dates
Calculating Paydates
In order to calculate the paydayes, we list all the dates between two specified dates. This is given by the following function.
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)
]
Once a list of days is produced, we create a function that filters the dates for the dates that represent actual pay dates. In the function below, we iterate all the dates. If the date falls on the end of the month, or on the 15th of the month, we assume that it is a pay day. If the date falls on a weekend, we find the first week day before the given date.
def pay_dates(dates):
results = []
for date in dates:
if date == get_end_of_month_iso(date) :results.append(get_nearest_past_weekday(date))
split = date.split('-')
if split[2] == '15': results.append(get_nearest_past_weekday(date))
return results
Computing Payroll
Once we have a set of paydates, we need a list of employees with salaries. We will assume that we have a set of employee records such as the following.
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 emmployee will have one or more records. The employee will have an id that identifies the employee. In addition, there will be a start date, and a possible end date, which indicates the end of the record timespan. The salary on the record is a per period salary figure.
We assume that if an employee receives a raise, that the old salary record will have an end date just prior to the date that the raise goes into effect, and then a new record is created that represents the new salary.
Then we iterate through all the dates, and for each employee record that is active at the given date, we record a salary payment. There is a complexity here, if the employee just started in the period, we need to calculate a fraction of the period for which to pay salary for.
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
Sample Code
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