Get Bi-Weekly Paydays And Major US Holidays With PowerShell

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}
#>