Populating U.S. Federal Holidays in a Calendar Table

The title of this blog is only half as thrilling as the SQL. I can assure you.

I have a calendar table at work that is based off of the calendar table that you can find on aspfaq.com in an article titled Why should I consider using an auxiliary calendar table?

The big problem is that the holidays in the script are not actually federal holidays. They’re just a set of holidays that seemed significant to the author at the time of writing. The Federal government actually has rules about how to calculate holidays as well as a great list of federal holidays.

One of the more interesting rules is that if a holiday falls on a Saturday, the holiday is observed on a Friday. If the holiday falls on Sunday, the holiday is observed on Monday. That means New Year’s Day sometimes happens last year.

The following query is pretty self-explanatory and basically uses a few look forwards and look backs to figure out when holidays end up falling on the weekend and it makes a second, observed, holiday on the closest weekday.

I have verified this from 1999 through 2020 just to make sure that my math was correct against all posted Federal Holidays.

I hope this saves somebody some time.

UPDATE dbo.Calendar
   SET IsHoliday = 0;

-- New Year's day
UPDATE Calendar
   SET IsHoliday = 1
 WHERE M = 1
   AND D = 1;

-- New Year's day observed
--  if NYD is a Saturday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 12
   AND c1.D = 31
   AND c1.DW = 6
   AND EXISTS (SELECT 1
                 FROM Calendar AS c2
                WHERE c2.M = 1
                  AND c2.D = 1
                  AND c2.[Date] > c1.[Date]
                  AND DW = 7);

-- if NYD is a Sunday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 1
   AND c1.D = 2
   AND c1.DW = 2
   AND EXISTS (SELECT 1
                 FROM Calendar AS c2
                WHERE c2.M = 1
                  AND c2.D = 1
                  AND c2.[Date] < c1.[Date]
                  AND c2.DW = 1);

-- Martin Luther King Day
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 1
   AND c1.DW = 2
   AND (SELECT COUNT(*)
          FROM Calendar AS c2
         WHERE c2.M = 1
           AND c2.DW = 2
           AND c2.Y = c1.Y
           AND c2.[Date] < c1.[Date]) = 2;

-- Washington's Birthday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 2
   AND c1.DW = 2
   AND (SELECT COUNT(*)
          FROM Calendar AS c2
         WHERE c2.M = 2
           AND c2.DW = 2
           AND c2.Y = c1.Y
           AND c2.[Date] < c1.[Date]) = 2;

-- Memorial Day
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE M = 5
   AND DW = 2
   AND NOT EXISTS (SELECT 1
                     FROM Calendar AS c2
                    WHERE M = 5 AND DW = 2
                      AND c2.Y = c1.Y
                      AND c2.[Date] > c1.[Date]);

-- Independence Day
UPDATE Calendar
   SET IsHoliday = 1
 WHERE M = 7
   AND D = 4;

-- Independence Day, observed
--  if Independence Day is a Saturday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 7
   AND c1.D = 3
   AND c1.DW = 6
   AND EXISTS (SELECT 1
                 FROM Calendar AS c2
                WHERE c2.M = 7
                  AND c2.D = 4
                  AND c2.DW = 7
                  AND c2.[Date] > c1.[Date]);

--  if Independence Day is a Sunday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 7
   AND c1.D = 5
   AND c1.DW = 2
   AND EXISTS (SELECT 1
                 FROM Calendar AS c2
                WHERE c2.M = 7
                  AND c2.D = 4
                  AND c2.DW = 1
                  AND c2.[Date] < c1.[Date]);

-- Labor Day
UPDATE Calendar
    SET IsHoliday = 1
   FROM Calendar AS c1
  WHERE M = 9
    AND DW = 2
    AND NOT EXISTS (SELECT 1
                      FROM Calendar AS c2
                     WHERE M = 9 AND DW = 2
                       AND c2.Y = c1.Y
                       AND c2.[Date] < c1.[Date]);

-- Columbus Day
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 10
   AND c1.DW = 2
   AND (SELECT COUNT(*)
          FROM Calendar AS c2
         WHERE c2.M = 10
           AND c2.DW = 2
           AND c2.Y = c1.Y
           AND c1.[Date] > c2.[Date]) = 1

-- Veterans' Day
UPDATE Calendar
   SET IsHoliday = 1
 WHERE M = 11
   AND D = 11 

-- Veterans' Day, observed
--  when Veterans' Day is a Saturday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 11
   AND c1.D = 10
   AND c1.DW = 6
   AND EXISTS (SELECT 1
                 FROM Calendar AS c2
                WHERE c2.M = 11
                  AND c2.D = 11
                  AND c2.DW = 7
                  AND c2.[Date] > c1.[Date]);

--  when Veterans' Day is a Sunday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 11
   AND c1.D = 12
   AND c1.DW = 2
   AND EXISTS (SELECT 1
                 FROM Calendar AS c2
                WHERE c2.M = 11
                  AND c2.D = 11
                  AND c2.DW = 1
                  AND c2.[Date] < c1.[Date]);

-- Thanksgiving Day
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE M = 11
   AND DW = 5
   AND (SELECT COUNT(*)
          FROM Calendar AS c2
         WHERE M = 11
           AND DW = 5
           AND c2.Y = c1.Y
           AND c2.[Date] < c1.[Date]) = 3;

-- Christmas Day
UPDATE Calendar
   SET IsHoliday = 1
 WHERE M = 12
   AND D = 25;

-- Christmas Day, observed
--  when Christmas Day is a Saturday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 12
   AND c1.D = 24
   AND c1.DW = 6
   AND EXISTS (SELECT 1
                 FROM Calendar AS c2
                WHERE c2.M = 12
                  AND c2.D = 25
                  AND c2.DW = 7
                  AND c2.[Date] > c1.[Date]);

--  when Christmas Day is a Sunday
UPDATE Calendar
   SET IsHoliday = 1
  FROM Calendar AS c1
 WHERE c1.M = 12
   AND c1.D = 26
   AND c1.DW = 2
   AND EXISTS (SELECT 1
                 FROM Calendar AS c2
                WHERE c2.M = 12
                  AND c2.D = 25
                  AND c2.DW = 1
                  AND c2.[Date] < c1.[Date]);

Comments

14 Comments so far. Leave a comment below.
  1. Ram,

    I don’t find words to appreciate your hardwork.

    Stupdendous!!!!!

    keep it up!!!!!

    Best Regards,
    Ram

  2. robert searle,

    Very well done. Hats off to you. It works perfectly. Many Many Thanks!!!

  3. Remi,

    Thanks for this query. It saved me many hours of work. I used it to update my data warehouse Calendar table and it worked perfectly!!

    Thanks,
    Remi.

  4. Curtis,

    This is a beautiful thing. I made a small change and added an IsFederalHoliday in addition to the IsHoliday column. Saved me lots and lots of work.

  5. Jim,

    Thanks so much, between your article and the “Why I should..” post, I finally have a robust solution for accurately calculating package delivery schedules. Not to mention a whole slew of other useful things. I exclusively work in Firebird, so I had to tweak the code a bit, but it works great. Willing to share if anyone needs it.

  6. Bob,

    How would you recommend adding production vs. non-production day for a manufacturing implementation?
    How would you recommend adding shifts for the same environment? Some have 2, some 3, some a mix depending on the deparment?

    • I would track production days with a separate bit column, ProductionDay, since production schedules aren’t related to days of the year.

      I would maintain a separate shifts table to describe the department/shift/day relationship. That one is a bit trickier and depends more on how that data will be used.

  7. Steve,

    Has anyone already altered this script in a way that only creates the observed holiday and not 2 instances of a given holiday if it occurs on a weekend?

    • I have not, but feel free to modify it. Typically we’ve wanted to know about both the observed and actual holiday, but I can see why you’d want one or the other. It’s always possible to add an extra column for observed holidays and use that instead. I’ve usually found it helpful to know when the real holiday occurs as well as when the bank is closed.

  8. Terry,

    How would you get the date for a query where you know the current date and you need the record that is 8 business days prior to that date?

  9. Terry,

    How would I populate that?

Add Your Comments

Disclaimer
Your email is never published nor shared.
Required
Required
Tips

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <ol> <ul> <li> <strong> <p>

Ready?

This site is protected with Urban Giraffe's plugin 'HTML Purified' and Edward Z. Yang's Powered by HTML Purifier. 226 items have been purified.