2

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.

R. Noa
  • 31
  • 4
  • So, which part of the code doesn't work as expected and what is the symptom? Please try to provide an [MCVE (Minimal, Complete, and Verifiable Example)](http://stackoverflow.com/help/mcve). – mklement0 Sep 22 '19 at 18:31
  • Maybe just hard code the next 5 years holiday dates and add a calendar reminder to update it in January 2027? :-) – mclayton Jan 17 '22 at 22:48

2 Answers2

1

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.

Rich Moss
  • 2,195
  • 1
  • 13
  • 18
  • Thank you very much for your solution. It's much more efficient. However, let's say it ran on 12/26/2016 which is the Monday after Christmas Day which falls on a Sunday. Monday would be the company holiday, the observance of Christmas Day, therefore I would want the script to return true and thus not run the subsequent script that would generate an email. – R. Noa Sep 23 '19 at 14:10
  • Found the `AddDays` bug and fixed it. The updated `IsHoliday` function also allows multiple holidays per month. Thanks for checking my work! – Rich Moss Sep 23 '19 at 19:20
0

RichMoss's answer was very helpful. (I don't have a high enough rep score to comment these additions to his code (new to stackoverflow), so I'll put it here)

I added some additional Holidays that we have:

(0..29 | %{([datetime]"1/1/$year").AddDays($_)}|?{$_.DayOfWeek -eq 'Monday'})[2], # Martin Luther King Jr. Day; third Monday in January

(0..29 | %{([datetime]"2/1/$year").AddDays($_)}|?{$_.DayOfWeek -eq 'Monday'})[2], # President's day; third Monday in February

[datetime]"11/11/$year", # Veterans Day; November 11 every year

(0..29 | %{([datetime]"11/1/$year").AddDays($_)}|?{$_.DayOfWeek -eq 'Thursday'})[3], # Thanksgiving; 4th Thu in Nov.

(0..29 | %{([datetime]"11/1/$year").AddDays($_)}|?{$_.DayOfWeek -eq 'Thursday'})[3].AddDays(1), # Day After Thanksgiving; 4th Thu in Nov +1.
CFCJB
  • 11
  • 4