Example PowerShell code to generate paydays and holidays with or without full calendar. The Holiday function could be shorter and more elegant, but it works for my needs.
<# Basic functions to get bi-weekly pay and major holidays. May need alterations on Windows or Mac. I am on Linux. Author: C. Nichols #> function Get-Paydays () { param ( [int]$FirstPayMonth = 1, [int]$FirstPayDay = 12, [int]$Year = 2024 ) $WeekNum = @{ Sunday=0; Monday=1; Tuesday=2; Wednesday=3; Thursday=4; Friday=5; Saturday=6 } if (-not($Year)) { $CurrentYear = (Get-Date).Year } else { $CurrentYear = $Year } $FirstPayOfYear = Get-Date -Year $CurrentYear -Month $FirstPayMonth -Day $FirstPayDay $EndOfYear = Get-Date -Year $CurrentYear -Month 12 -Day 31 $CurrentDate = $FirstPayOfYear $Paydays = New-Object -TypeName "System.Collections.ArrayList" While ($CurrentDate -lt $EndOfYear) { If ($CurrentDate.DayOfWeek -eq 'Friday') { $MonthName = (Get-Culture).DateTimeFormat.GetMonthName($CurrentDate.Month) $DayName = $CurrentDate.DayOfWeek.ToString() $PayDate = [PsCustomObject]@{ MonthName=$MonthName; Month=$CurrentDate.Month; WeekIndex=$WeekNum[$DayName]; DayName=$DayName; Day=$CurrentDate.Day; Year=$CurrentYear; Desc="payday" } $Paydays += $PayDate } $CurrentDate = $CurrentDate.AddDays(14) } return $Paydays } function Get-Holidays() { PARAM ( [int]$Year=2024 ) $WeekNum = @{ Sunday=0; Monday=1; Tuesday=2; Wednesday=3; Thursday=4; Friday=5; Saturday=6 } $FIXED_HOLIDAYS = @{12=@{25="Christmas Day"}; 1=@{1="New Year's Day"}; 6=@{19="Juneteenth"}; 7=@{4="Independence Day"}; 11=@{11="Veteran's Day"} } $HOLIDAYS = @{1=@{3="Martin Luther King, Jr.Birthday"}; # 3rd Monday in Jan. 2=@{3="President's Day"}; # 3rd Monday in Feb. (President's Day) #5="memorial"; # Last Monday in May. Handled separately. 9=@{1="Labor Day"}; # 1st Monday in Sept. 10=@{2="Columbus Day"}; # 2nd Monday in Oct. 11=@{4="Thanksgiving"} # 4th Thursday in Nov. } $HolidayCalendar = @{} $Calendar = @{} $Rows = New-Object -TypeName "System.Collections.ArrayList" if (-not($Year)) { $Year = (Get-Date).Year } # Link month name to day names for year. ForEach ($Month in 1..12) { $DaysInMonth = [datetime]::DaysInMonth($Year,$Month) ForEach ($Day in 1..$DaysInMonth) { $DateObject = Get-Date -Year $Year -Month $Month -Day $Day $DayName = $DateObject.DayOfWeek if (-not($Calendar.ContainsKey($Month))) { $Calendar[$Month] = @{} } $Calendar[$Month][$Day] = $DayName } } # Get a increamental count of each weekday in month. $MonthKeys = $Calendar.Keys ForEach ($MonthKey in $MonthKeys | Sort-Object) { $DayHash = $Calendar[$MonthKey] $MonCount = 0 $MemArray = @() $DayKeys = $DayHash.Keys ForEach ($DayKey in $DayKeys | Sort-Object) { $MonthDays = $DayHash[$DayKey] $Holiday = "" if ($FIXED_HOLIDAYS.ContainsKey($MonthKey)) { $Holiday = $FIXED_HOLIDAYS[$MonthKey][$DayKey] } if ($MonthDays -eq "Monday") { $MonCount += 1 if ($HOLIDAYS.ContainsKey($MonthKey)) { if ($HOLIDAYS[$MonthKey].ContainsKey($MonCount)) { $Holiday = $HOLIDAYS[$MonthKey][$MonCount] } } } if ($MonthKey -eq 5) { if ($MonthDays -eq "Monday") { $MemArray += $DayKey } } if (-not($HolidayCalendar.ContainsKey($MonthKey))) { $HolidayCalendar[$MonthKey] = @{} } if (-not($HolidayCalendar[$MonthKey].ContainsKey($DayKey))) { $HolidayCalendar[$MonthKey][$DayKey] = "" } $HolidayCalendar[$MonthKey][$DayKey] = $Holiday } if ($MemArray[-1]) { $Memday = $MemArray[-1] $HolidayCalendar[5][$Memday] = "Memorial Day" } } $MthKeys = $HolidayCalendar.Keys ForEach ($mk in $MthKeys | Sort-Object) { $MonthName = (Get-Culture).DateTimeFormat.GetMonthName($mk) #Write-Host $MonthName $DayKeys = $HolidayCalendar[$mk].Keys ForEach ($dk in $DayKeys | Sort-Object) { $DateObject = Get-Date -Year $Year -Month $mk -Day $dk $DayName = $DateObject.DayOfWeek.ToString() $HolidayDesc = $null if ($HolidayCalendar[$mk][$dk]) { $HolidayDesc = $HolidayCalendar[$mk][$dk] } if ($DayName -eq "Saturday") { $DateObject = $DateObject.AddDays(-1) $DayName = $DateObject.DayOfWeek.ToString() } if ($DayName -eq "Sunday") { $DateObject = $DateObject.AddDays(1) $DayName = $DateObject.DayOfWeek.ToString() } $Columns = [PsCustomObject]@{ MonthName=$MonthName; Month=$mk; WeekIndex=$WeekNum[$DayName]; DayName=$DayName; Day=$dk; Year=$Year; Desc=$HolidayDesc } $Rows += $Columns } } return $Rows } <# Main :: Merge and offset clashes. #> $WeekNum = @{ Sunday=0; Monday=1; Tuesday=2; Wednesday=3; Thursday=4; Friday=5; Saturday=6 } $MergedRows = @() $DateHash = @{} # Ge major holidays. $MyHolidays = Get-Holidays #-Year 2026 $MyPaydays = Get-Paydays #-Year 2026 -FirstPayMonth 1 -FirstPayDay 9 ForEach ($p in $MyPaydays) { $Pdate = Get-Date -Year $p.Year -Month $p.Month -Day $p.Day if (-not($DateHash.ContainsKey($Pdate.Date))) { $DateHash[$Pdate.Date] = $p } } ForEach ($h in $MyHolidays) { if ($h.Desc) { $Hdate = Get-Date -Year $h.Year -Month $h.Month -Day $h.Day if (-not($DateHash.ContainsKey($Hdate.Date))) { $DateHash[$Hdate.Date] = $h } else { $Pdate = Get-Date -Year $DateHash[$Hdate.Date].Year -Month $DateHash[$Hdate.Date].Month -Day $DateHash[$Hdate.Date].Day $Pdate = $Pdate.AddDays(-1) $MonthName = (Get-Culture).DateTimeFormat.GetMonthName($Pdate.Month) $DayName = $Pdate.DayOfWeek.ToString() $Column = [PsCustomObject]@{ MonthName=$MonthName; Month=$Pdate.Month; WeekIndex=$WeekNum[$DayName]; DayName=$DayName; Day=$Pdate.Day; Year=$Pdate.Year; Desc="payday" } $DateHash[$Hdate.Date] = $h $DateHash[$Pdate.Date] = $Column } } } # create report. $ReportRows = @() $DateKeys = $DateHash.Keys ForEach ($mdate in $DateKeys | Sort-Object) { Write-Host $DateHash[$mdate] $ReportRows += $DateHash[$mdate] } # Save as CSV. #$ReportRows | Export-Csv -Path "~/PrCal.csv" -NoTypeInformation <# OUTPUT @{MonthName=January; Month=1; WeekIndex=1; DayName=Monday; Day=1; Year=2024; Desc=New Year's Day} @{MonthName=January; Month=1; WeekIndex=5; DayName=Friday; Day=12; Year=2024; Desc=payday} @{MonthName=January; Month=1; WeekIndex=1; DayName=Monday; Day=15; Year=2024; Desc=Martin Luther King, Jr.Birthday} @{MonthName=January; Month=1; WeekIndex=5; DayName=Friday; Day=26; Year=2024; Desc=payday} @{MonthName=February; Month=2; WeekIndex=5; DayName=Friday; Day=9; Year=2024; Desc=payday} @{MonthName=February; Month=2; WeekIndex=1; DayName=Monday; Day=19; Year=2024; Desc=President's Day} @{MonthName=February; Month=2; WeekIndex=5; DayName=Friday; Day=23; Year=2024; Desc=payday} @{MonthName=March; Month=3; WeekIndex=5; DayName=Friday; Day=8; Year=2024; Desc=payday} @{MonthName=March; Month=3; WeekIndex=5; DayName=Friday; Day=22; Year=2024; Desc=payday} @{MonthName=April; Month=4; WeekIndex=5; DayName=Friday; Day=5; Year=2024; Desc=payday} @{MonthName=April; Month=4; WeekIndex=5; DayName=Friday; Day=19; Year=2024; Desc=payday} @{MonthName=May; Month=5; WeekIndex=5; DayName=Friday; Day=3; Year=2024; Desc=payday} @{MonthName=May; Month=5; WeekIndex=5; DayName=Friday; Day=17; Year=2024; Desc=payday} @{MonthName=May; Month=5; WeekIndex=1; DayName=Monday; Day=27; Year=2024; Desc=Memorial Day} @{MonthName=May; Month=5; WeekIndex=5; DayName=Friday; Day=31; Year=2024; Desc=payday} @{MonthName=June; Month=6; WeekIndex=5; DayName=Friday; Day=14; Year=2024; Desc=payday} @{MonthName=June; Month=6; WeekIndex=3; DayName=Wednesday; Day=19; Year=2024; Desc=Juneteenth} @{MonthName=June; Month=6; WeekIndex=5; DayName=Friday; Day=28; Year=2024; Desc=payday} @{MonthName=July; Month=7; WeekIndex=4; DayName=Thursday; Day=4; Year=2024; Desc=Independence Day} @{MonthName=July; Month=7; WeekIndex=5; DayName=Friday; Day=12; Year=2024; Desc=payday} @{MonthName=July; Month=7; WeekIndex=5; DayName=Friday; Day=26; Year=2024; Desc=payday} @{MonthName=August; Month=8; WeekIndex=5; DayName=Friday; Day=9; Year=2024; Desc=payday} @{MonthName=August; Month=8; WeekIndex=5; DayName=Friday; Day=23; Year=2024; Desc=payday} @{MonthName=September; Month=9; WeekIndex=1; DayName=Monday; Day=2; Year=2024; Desc=Labor Day} @{MonthName=September; Month=9; WeekIndex=5; DayName=Friday; Day=6; Year=2024; Desc=payday} @{MonthName=September; Month=9; WeekIndex=5; DayName=Friday; Day=20; Year=2024; Desc=payday} @{MonthName=October; Month=10; WeekIndex=5; DayName=Friday; Day=4; Year=2024; Desc=payday} @{MonthName=October; Month=10; WeekIndex=1; DayName=Monday; Day=14; Year=2024; Desc=Columbus Day} @{MonthName=October; Month=10; WeekIndex=5; DayName=Friday; Day=18; Year=2024; Desc=payday} @{MonthName=November; Month=11; WeekIndex=5; DayName=Friday; Day=1; Year=2024; Desc=payday} @{MonthName=November; Month=11; WeekIndex=1; DayName=Monday; Day=11; Year=2024; Desc=Veteran's Day} @{MonthName=November; Month=11; WeekIndex=5; DayName=Friday; Day=15; Year=2024; Desc=payday} @{MonthName=November; Month=11; WeekIndex=1; DayName=Monday; Day=25; Year=2024; Desc=Thanksgiving} @{MonthName=November; Month=11; WeekIndex=5; DayName=Friday; Day=29; Year=2024; Desc=payday} @{MonthName=December; Month=12; WeekIndex=5; DayName=Friday; Day=13; Year=2024; Desc=payday} @{MonthName=December; Month=12; WeekIndex=3; DayName=Wednesday; Day=25; Year=2024; Desc=Christmas Day} @{MonthName=December; Month=12; WeekIndex=5; DayName=Friday; Day=27; Year=2024; Desc=payday} #>