Calculating holidays for a given year in PHP

Many stores and offices are closed on holidays. You may want to display an interval when the company is open, and a closed sign when it's closed, taking holidays into account.

Fixed holidays like Christmas and New Year's day are celebrated on the same day every year, whereas variable holidays like Thanksgiving or Labor Day don't have a fixed date, but there's always a formula that can be used to calculate it.

Fixed holidays

Fixed holidays are holidays that are celebrated on the same day every year. For example, Christmas is always celebrated on December 25th.

Since these holidays are always celebrated on the same day, there's not much to calculate. We can simply check if a given date is equal to the holiday date.

Let's start by writing a function that checks if a given date is the Christmas date. We'll format the date in a way that makes it easy to read, then compare it to December 25th.

holidays.php
<?php

function is_christmas(DateTimeInterface $date): bool {
    return 
$date->format('m-d') === '12-25';
}

Calling is_christmas(new DateTime('2024-12-25')) will return true, which is what we wanted. Nice and easy.

Christmas is a fixed holiday, but there are many others. Let's write a function that returns a list of fixed holidays for a given year. For this, we can take a look at the official list of US Federal Holidays and include the fixed dates in our function.

holidays.php
<?php

/** @return array<string, DateTimeInterface> */
function get_holidays(int $year): array {
    return [
        
'new_years_day' => new DateTime("$year-01-01"),
        
'juneteenth' => new DateTime("$year-06-19"),
        
'independence_day' => new DateTime("$year-07-04"),
        
'veterans_day' => new DateTime("$year-11-11"),
        
'christmas' => new DateTime("$year-12-25"),
    ];
}

Now that we have a list of holidays, we can check if any given date is a holiday by looping through each holiday and comparing the given date to the holiday date. Let's write a is_holiday() function that does this.

holidays.php
<?php

/** @return array<string, DateTimeInterface> */
function get_holidays(int $year): array {
    return [
        
'new_years_day' => new DateTime("$year-01-01"),
        
'juneteenth' => new DateTime("$year-06-19"),
        
'independence_day' => new DateTime("$year-07-04"),
        
'veterans_day' => new DateTime("$year-11-11"),
        
'christmas' => new DateTime("$year-12-25"),
    ];
}

function 
is_holiday(DateTimeInterface $date): bool {
    
$year = (int) $date->format('Y');

    
$date_formatted $date->format('Y-m-d');
    foreach (
get_holidays($year) as $holiday) {
        
$holiday_formatted $holiday->format('Y-m-d');
        if (
$date_formatted === $holiday_formatted) {
            return 
true;
        }
    }

    return 
false;
}

With this function complete, we can write another function that returns the opening hours, which we can then use on the website to show a closed sign when the store is closed, or the opening hours otherwise.

hours.php
<?php

require_once 'holidays.php';

function 
get_hours(): string {
    
$now = new DateTime();

    if (
is_holiday($now)) {
        return 
'Closed';
    } else {
        return 
'9:00 AM - 5:00 PM';
    }
}

If the store is closed on weekends, we can add a check for that as well. We can use DateTime::format('N') to get the day of the week as a number, where 1 is Monday and 7 is Sunday, then check if the day of the week is 6 or 7, which are Saturday and Sunday respectively. Take a look at DateTime::format() for more details about the format string.

hours.php
<?php

require_once 'holidays.php';

function 
get_hours(): string {
    
$now = new DateTime();

    
$is_holiday is_holiday($now);
    
$is_weekend = (int) $now->format('N') >= 6;

    if (
$is_holiday || $is_weekend) {
        return 
'Closed';
    } else {
        return 
'9:00 AM - 5:00 PM';
    }
}

Nice. We can now show the opening hours, and we're taking holidays and weekends into account. Also, since holidays are listed in the get_holidays() function, we can easily add or remove holidays, and the opening hours will automatically update.

We could add company specific holidays, or maybe legal holidays that are different from country to country. We could also remove holidays which might not be relevant to the business, such as maybe Christmas for a store that sells Christmas decorations.

With fixed holidays handled, let's now look into variable holidays.

Variable holidays

Variable holidays are a bit more complicated to calculate, since they're not always on the same day. For example, Thanksgiving day is the fourth Thursday of November, and Easter is famously complicated to calculate – it involves full moons and equinoxes!

Luckily, PHP can help a lot when it comes to calculating dates.

To calculate the fourth Thursday of November 2024, we can simply instantiate a new DateTime('fourth Thursday of November 2024') and the correct date of November 28 2024 will be calculated. Take a look at the Supported Date and Time Formats documentation article for more information about the format string.

With this in mind, let's look back at the US Federal Holidays and include the variable dates in our get_holidays() function.

holidays.php
<?php

/** @return array<string, DateTimeInterface> */
function get_holidays(int $year): array {
    return [
        
// Fixed holidays

        
'new_years_day' => new DateTime("$year-01-01"),
        
'juneteenth' => new DateTime("$year-06-19"),
        
'independence_day' => new DateTime("$year-07-04"),
        
'veterans_day' => new DateTime("$year-11-11"),
        
'christmas' => new DateTime("$year-12-25"),

        
// Variable holidays

        
'martin_luther_king_day' => new DateTime(
            
"third Monday of January $year"
        
),
        
'washington_day' => new DateTime(
            
"third Monday of February $year"
        
),
        
'memorial_day' => new DateTime(
            
"last Monday of May $year"
        
),
        
'labor_day' => new DateTime(
            
"first Monday of September $year"
        
),
        
'columbus_day' => new DateTime(
            
"second Monday of October $year"
        
),
        
'thanksgiving_day' => new DateTime(
            
"fourth Thursday of November $year"
        
),
    ];
}

function 
is_holiday(DateTimeInterface $date): bool {
    
$year = (int) $date->format('Y');

    
$date_formatted $date->format('Y-m-d');
    foreach (
get_holidays($year) as $holiday) {
        
$holiday_formatted $holiday->format('Y-m-d');
        if (
$date_formatted === $holiday_formatted) {
            return 
true;
        }
    }

    return 
false;
}

Easy! PHP does all the hard work for us.

The US Federal Holidays page also says that if you live in Washington DC, there's an additional holiday called the Inauguration Day, which falls on January 20th of every 4th year, unless that day falls on a weekend, in which case it's moved one day forwards. We can add this to our get_holidays() function as well.

holidays.php
<?php

/** @return array<string, DateTimeInterface> */
function get_holidays(int $year): array {
    
$holidays = [
        
// Fixed holidays

        
'new_years_day' => new DateTime("$year-01-01"),
        
'juneteenth' => new DateTime("$year-06-19"),
        
'independence_day' => new DateTime("$year-07-04"),
        
'veterans_day' => new DateTime("$year-11-11"),
        
'christmas' => new DateTime("$year-12-25"),

        
// Variable holidays

        
'martin_luther_king_day' => new DateTime(
            
"third Monday of January $year"
        
),
        
'washington_day' => new DateTime(
            
"third Monday of February $year"
        
),
        
'memorial_day' => new DateTime(
            
"last Monday of May $year"
        
),
        
'labor_day' => new DateTime(
            
"first Monday of September $year"
        
),
        
'columbus_day' => new DateTime(
            
"second Monday of October $year"
        
),
        
'thanksgiving_day' => new DateTime(
            
"fourth Thursday of November $year"
        
),
    ];

    
// Inauguration Day, every 4th year

    
if ($year === 0) {
        
$inauguration_day = new DateTime("$year-01-20");

        if (
$inauguration_day->format('l') === 'Sunday') {
            
$inauguration_day->modify('+1 day');
        }

        
$holidays['inauguration_day'] = $inauguration_day;
    }

    return 
$holidays;
}

function 
is_holiday(DateTimeInterface $date): bool {
    
$year = (int) $date->format('Y');

    
$date_formatted $date->format('Y-m-d');
    foreach (
get_holidays($year) as $holiday) {
        
$holiday_formatted $holiday->format('Y-m-d');
        if (
$date_formatted === $holiday_formatted) {
            return 
true;
        }
    }

    return 
false;
}

A bit more complicated, but still pretty easy, since PHP has all the tools we need to implement this logic.

But what if the store is also closed on Easter? Easter date is the first Sunday after the first full moon during or after the Spring Equinox. There's a long Wikipedia article about calculating this date, but PHP has us covered with the easter_days() function which returns the number of days we need to add to March 21st, the last day on which the Spring Equinox can occur. Let's use it to add Easter to our list of holidays.

holidays.php
<?php

/** @return array<string, DateTimeInterface> */
function get_holidays(int $year): array {
    
$holidays = [
        
// Fixed holidays

        
'new_years_day' => new DateTime("$year-01-01"),
        
'juneteenth' => new DateTime("$year-06-19"),
        
'independence_day' => new DateTime("$year-07-04"),
        
'veterans_day' => new DateTime("$year-11-11"),
        
'christmas' => new DateTime("$year-12-25"),

        
// Variable holidays

        
'martin_luther_king_day' => new DateTime(
            
"third Monday of January $year"
        
),
        
'washington_day' => new DateTime(
            
"third Monday of February $year"
        
),
        
'memorial_day' => new DateTime(
            
"last Monday of May $year"
        
),
        
'labor_day' => new DateTime(
            
"first Monday of September $year"
        
),
        
'columbus_day' => new DateTime(
            
"second Monday of October $year"
        
),
        
'thanksgiving_day' => new DateTime(
            
"fourth Thursday of November $year"
        
),
    ];

    
// Inauguration Day, every 4th year

    
if ($year === 0) {
        
$inauguration_day = new DateTime("$year-01-20");

        if (
$inauguration_day->format('l') === 'Sunday') {
            
$inauguration_day->modify('+1 day');
        }

        
$holidays['inauguration_day'] = $inauguration_day;
    }

    
// Easter

    
$easter = new DateTime("$year-03-21");
    
$easter->modify('+' easter_days($year) . ' days');
    
$holidays['easter'] = $easter;

    return 
$holidays;
}

function 
is_holiday(DateTimeInterface $date): bool {
    
$year = (int) $date->format('Y');

    
$date_formatted $date->format('Y-m-d');
    foreach (
get_holidays($year) as $holiday) {
        
$holiday_formatted $holiday->format('Y-m-d');
        if (
$date_formatted === $holiday_formatted) {
            return 
true;
        }
    }

    return 
false;
}

This is now a pretty complete list of holidays, from the simplest to the most complex to calculate. And since it's all part of a function, it can easily be modified to add or remove holidays, as needed.

Conclusion

As you can see, it's pretty easy to calculate holidays using PHP.

While this example involves mainly US holidays, it can be adapted to cover any country's holidays. For example, you could have Valentine's Day, new DateTime("$year-02-14"), which is celebrated in many countries, or maybe Family Day, a Canadian holiday which falls on the third Monday of February, new DateTime("third Monday of February $year"), or any other holiday you might need to take into account.