How can I check to see if today is a holiday

We've got some scripts that run inside of scheduled tasks that send us email alerts to remind us to take action on some things in our environment daily. I'd like to prefix the script(s) with logic to determine if today is a company observed holiday and not send the email if it is, because no one wants to think about work on a holiday! Here's what I've come up with.

I'm only running the code if today is not a weekend day. I'm creating an array with a list of set holidays that don't vary. I'm then checking to see if today is one of those set holidays and if it is then I return true. If it's not a set holiday, I'm then checking to see if it's a holiday that varies like Memorial, Labor, and Thanksgiving days. Last, I'm checking to see if it's Friday or Monday, and if it is - check to see if a set holiday falls on the weekend and is therefore observed a day early or a day late.

<#
Company Holidays:
 • New Year’s Day – 1st day of January
 • Memorial Day – Last Monday in May
 • Independence Day – 4th day of July
 • Labor Day – 1st Monday in September
 • Thanksgiving Day – 4th Thursday in November
 • Christmas Day – 25th day of December

Day of Observance: A holiday will be observed on the holiday itself, except when the following conditions apply:
 1. A holiday falling on a Saturday will be observed on the preceding Friday.
 2. A holiday falling on Sunday will be observed on the following Monday.

#>

#We already know it's not a weekend because this code is only invoked if the day of the week does not start with S*.

function fn-get-variable-holiday ($Month, $DayofWeek, $DayofMonth, $IsHoliday)
{
    if ($Month -eq 5)
    {
        if ($DayofWeek -eq 'Monday' -and $DayofMonth -ge 25)
        {
            #it's the first Monday of September, Labor Day
            $IsHoliday = $true
            Return $IsHoliday
            Break
        }
    }

    if ($Month -eq 9)
    {
        if ($DayofWeek -eq 'Monday' -and $DayofMonth -le 7)
        {
            #it's the first Monday of September, Labor Day
            $IsHoliday = $true
            Return $IsHoliday
            Break
        }
    }

    if ($Month -eq 11)
    {
        if ($DayofWeek -eq 'Thursday' -and ($DayofMonth -ge 22 -and ($DayofMonth -le 28)))
        {
            #it's the fourth Thursday of November, Thanksgiving Day
            $IsHoliday = $true
            Return $IsHoliday
        }
    }
}

Function fn-get-weekend-set-holiday ($DayofWeek, $DayofMonth, $IsHoliday) {

    if ($DayofWeek -eq 'Monday') #Check to see if yesterday was a Holiday that we observe today
    {
        $IsHoliday = fn-get-set-holiday -Today $Today.AddDays(-1)
    } 
    elseif ($DayofWeek -eq 'Friday') #Check to see if tomorrow is a Holiday that we observe today
    {
        $IsHoliday = fn-get-set-holiday -Today $Today.AddDays(1)
    }

    Return $IsHoliday
}

Function fn-get-set-holiday ($Today, $SetHolidays, $IsHoliday)
{
    $SetHolidays = @("01/01","07/04","12/25")

    foreach ($H in $SetHolidays)
    {
        if ($today -like "*$H*")
        {
            $IsHoliday = $true
            Return $IsHoliday
            Break
        }
    }
}

Function Main
{
    #$Today = Get-Date
    $Today = (Get-Date).AddDays(67)
    $IsHoliday = $false

    Write-Host "`nToday is $($Today.ToLongDateString())..."

    if (fn-get-set-holiday -Today $Today -SetHolidays $SetHolidays -IsHoliday $IsHoliday)
    { 
        Write-Host "It's a set holiday"
        Return $true
    }
        elseif (fn-get-variable-holiday -Month $Today.Month -DayofWeek $today.DayOfWeek -DayofMonth $Today.Day -IsHoliday $IsHoliday)
        {
            Write-Host "It's a variable holiday"
            Return $true
        }
            elseif (fn-get-weekend-holiday -DayofWeek $today.DayOfWeek -DayofMonth $Today.Day -IsHoliday $IsHoliday)
            {
                Write-Host "It's a weekend holiday"
                Return $true
            }


}


Main

I expect the script to output True if today is the observance of a holiday, and nothing if it is not.

Feel free to provide feedback on how to improve the script for syntax or efficiency.


If you are not having functional problems with the code, you should submit it to the CodeReview site for hints to improve style or performance. That said, this is such a useful algorithm I thought it would be fun to rewrite exactly per the comments.

function IsHoliday([datetime] $DateToCheck = (Get-Date)){
  [int]$year = $DateToCheck.Year
  If($DateToCheck.Day -eq 31 -and $DateToCheck.Month -eq 12 -and $DateToCheck.DayOfWeek -eq 'Friday'){$year = $year + 1}
  $HolidaysInYear = (@(
    [datetime]"1/1/$year", #New Year's Day on Saturday will be observed on 12/31 of prior year
    (23..30 | %{([datetime]"5/1/$year").AddDays($_)}|?{$_.DayOfWeek -eq 'Monday'})[-1], #Memorial Day
    $(If($year -ge 2021){[datetime]"6/19/$year"}Else{[datetime]"1/1/$year"}), #Juneteenth is a federal holiday since 2021
    [datetime]"7/4/$year",#Independence Day
    (0..6 | %{([datetime]"9/1/$year").AddDays($_)}|?{$_.DayOfWeek -eq 'Monday'})[0], #Labor Day - first Mon in Sept.
    (0..29 | %{([datetime]"11/1/$year").AddDays($_)}|?{$_.DayOfWeek -eq 'Thursday'})[3],#Thanksgiving - last Thu in Nov.
    [datetime]"12/25/$year"#Christmas
  ) | %{$_.AddDays($(If($_.DayOfWeek -eq 'Saturday'){-1}) + $(If($_.DayOfWeek -eq 'Sunday'){+1})) })
  Return $HolidaysInYear.Contains($DateToCheck.Date)
}

Here's the code I used to test it:

0..364|%{([datetime]'1/1/2019').AddDays($_)} | ?{IsHoliday $_}

It gets all the days of the year and only returns those that are holidays.

You can modify it slightly to return only workdays:

0..364|%{([datetime]'1/1/2016').AddDays($_)} | ?{!(IsHoliday $_) -and !@('Saturday','Sunday').Contains($_.DayOfWeek)}

EDIT: I've modified the function to add Juneteenth, acknowledging that it's not specified in the original question, but is a new Federal holiday since 2021.

I also found a bug for 12/31/2021 - an interesting date because New Year's Day was celebrated last year. It was confusing that this test was not returning the New Year's Day holiday for 2022:

-1..364|%{([datetime]'1/1/2022').AddDays($_)} | ?{IsHoliday $_}

... until I realized that my algorithm assumes that the holidays are in DateToCheck's year.

I added a check for the last day of the year being a Friday and incremented the year if so. There's probably a cleaner way to do this, but it successfully returns that 12/31/2021 was a holiday.