Python Could Know Your Holidays No Matter Which Country You Live

During our work in Software Development and Data Analytics, we love the business logic that can be accurately modelled. However, most of the time we are dealing with a real world that consists of many rules that do not follow any patterns. One of the best examples is the public holidays. Holidays could be very different in various countries even their subdivisions. If our program needs to deal with dates, very unlikely we can bypass holidays.
Of course, the best solution for this is to create a dimension table in your backend database and update it every year. However, sometimes our program needs to deal with a large number of different countries, or there is too much overhead to store all the holidays in the backend database if your application is small-scoped. Sometimes, our applications or data analytics environment may even don't have a backend database.
In this case, the "Holidays" Library can be our lifesaver. It has all the holidays from 141 different countries. Also, many built-in useful tricks make it very easy to use. Let me introduce you to this amazing library in this article.
1. Quick Start

As usual, we will start by installing the library. As the name of the Holidays library reflects its function very well, I can even memorise it.
$ pip install holidays
Before we can use it, let's import the library. Also, since we are going to work with dates, we will need to import the Python built-in DateTime library as well.
from datetime import date
import holidays

1.1 Creating A Holiday Object
As the first step for any use cases, we must create an object with all the holidays. Because holidays are country-dependent, they need to be initialised with certain locations such as country names.
For example, if we want to create an object with all the Australian Holidays, the following code will do it.
au_holidays = holidays.AU()
# is equivelant to
au_holidays = holidays.country_holidays('AU')
You may be curious about what is in this object. In fact, not much magic, the content of this object is a dictionary. We can check this by accessing its items()
attribute.
au_holidays.items()

1.2 Checking If a Date is a Holiday
As shown above, the holiday object inherits the Python Dictionary. So, if we want to check if a data is a holiday, it will be as easy as treating the date as the key, and checking if the key is in the dictionary.
date(2024, 1, 1) in au_holidays

If we are checking a date that is not a holiday, of course, it will return false.
date(2024, 1, 2) in au_holidays

1.3 Holidays in Different Countries
As we initialised the holiday object using a country name, the object will automatically hold holidays from that country. Here is a simple example using the 2024 Lunar New Year, which is the today I'm writing this article right now – Feb 10th.
If we check if the date is a holiday in Australia, of course, it's not.
date(2024, 2, 10) in au_holidays

However, if we check whether it is a holiday in China, of course, the answer is yes.
date(2024, 2, 10) in holidays.CN()

1.4 Some Flexibility of Usage
You may think every time we have to create a Python datetime object like this date(2024, 1, 1)
, which is too much overhead if we are dealing with other types such as strings.
Don't worry, we can use strings as well.
'2024-02-10' in holidays.CN()

If we want to check the holiday names of a date, we can also use strings.
au_holidays.get('2024-12-26')

Not satisfy with string only? If your project is dealing with Unix timestamp, you can directly use it as well.
# The timestamp below is '2024-02-10'
1707523200 in holidays.CN()

2. Performance Considerations

I have to highlight that some of the above examples are for convenient purposes only, which might not be the best practice in terms of performance.
For example, the statement holidays.CN()
will create a holiday object. That will definitely need more resources. Therefore, unless we are going to use it only once, creating an object for use every time is absolutely not a good idea.
Let's write some code to prove it. I need to highlight that the magic command would only be available in Jupyter Notebook or Google Colab.
%%timeit
date(2024, 2, 10) in holidays.CN()

If we create the object first, and then keep reusing this object to check if the date is a holiday in China, the performance will be much better.
cn_holidays = holidays.CN()
%%timeit
date(2024, 2, 10) in cn_holidays

So, the performance can be 1000 times different. Just bear in mind to reuse the holiday object if we can.
3. Many Other Useful Tricks

So far, we have introduced only the basic usage of the Holidays library. As a comprehensive library, there are also many detailed features and tricks available. I believe they will provide more benefits to us. In this section, let me introduce some of them.
3.1 Subdivisions of A Country
It is common for many countries that consist of multiple parts to have different holidays. The US is one of the examples, and so does Australia.
For example, we can add subdiv='VIC'
to create a holiday object for the Victoria state of Australia. Similarly, we can also add subdiv='NSW'
for the New South Wales state.
holidays.AU(subdiv='VIC', years=2024)
holidays.AU(subdiv='NSW', years=2024)
You may also realise in the above code we can provide the year to ask the holiday object to get the holidays for a certain year.
We can easily check the difference by listing all the dates along with the holiday names. The reason why I want to create the holiday objects with a certain is for easier comparison purposes.
for date, name in sorted(holidays.AU(subdiv='VIC', years=2024).items()):
print(date, name)
for date, name in sorted(holidays.AU(subdiv='NSW', years=2024).items()):
print(date, name)
In the above code, we use items()
to get all the holidays in the dictionary. Then, we can use sorted()
function to sort the holidays by key, which means the dates. After that, we get the date
and the name
from the dictionary and print them out. The result is as follows.

As shown, the Labour Day in these two states is different. Also, Victoria has Grand Final Day and Melbourne Cup which are local public holidays only, while New South Wales has a Bank Holiday.
3.2 Expand the Years On Demand
You may also notice that in the above example, I have specified year=2024
. Then, when we try to list all the holidays in the object, it indeed lists the holidays in 2024 only.
How about the other years? How does this work? Let me show you.
Firstly, let's create a new holiday object and try to list all the holidays in it. We can also access its years
attribute to see what years are in it.
au_holidays = holidays.AU()
for date, name in sorted(au_holidays.items()):
print(date, name)
au_holidays.years

Hmmm… There were no holidays printed at all, and the years
attribute is also an empty set. How come? Does that mean the object would not work? Let's give it a try. Testing if 2024-01-01
is a holiday.
'2024-01-01' in au_holidays

It works! But that's a bit confusing. Let's check its years
attribute and list the holidays again. The code was available above so I'll give the results only.

Now it has the years
which is 2024, and the holidays also become available in the object. How about let's try another test with the New Year's Day in 2023 and then try those again?

Now, the object has both the two years 2023 and 2024, and all their holidays in the dictionary.
Therefore, it is understandable that the holiday object can be treated as a "lazy" object that will only obtain holidays when necessary. This is a great design in my opinion, because we are not likely to require holidays for too many years. This lazy object design could save lots of memory and improve performance.
What if we want to deliberately exclude the dates from other years? Yes, there is a boolean attribute call expand=True
by default. If we set it to false, the holiday object will be "frozen" to the current years
without automatically expanding. For example, if we set expand=False
and test the Christmas in 2022, it will return False.
au_holidays.expand = False
'2022-12-25' in au_holidays

Of course, we can change it back to expand=True
, it will be able to automatically expand again.
au_holidays.expand = True
'2022-12-25' in au_holidays

3.3 Multi-Language Support
The library also supports multiple languages. For example, if we want to show the Chinese holidays in Chinese language, we just need to specify language='zh_CN'
when we create the holiday object.
for dt, name in sorted(holidays.CN(years=2024, language="zh_CN").items()):
print(dt, name)

3.4 Combining Holidays
This is another very useful feature. For example, if you have a company across two states and want to get holidays for both states together, that's as easy as using the plus sign +
to add up two holiday objects.
Suppose we want to combine the holiday list from Victoria and New South Wales in Australia together, we can do the following.
au_holidays_vic_nsw = holidays.country_holidays('AU', subdiv='NSW', years=2024) +
holidays.country_holidays('AU', subdiv='VIC', years=2024)
au_holidays_vic_nsw.subdiv

The attribute subdiv
shows that there are two subdivisions in this object. Then, let's check the holiday list.
for dt, name in sorted(au_holidays_vic_nsw.items()):
print(dt, name)

Look, now we have a list of holidays for both states.
Summary

In this article, I have introduced a 3rd-party Python library called holidays
. It can help us to get all the public holidays from most of the major countries in the world. In case we don't want to create a dimension table with all the holidays manually, this could be the best lightweight solution.
It can not only tell us if a date is a holiday in a country but also provide many useful tricks such as subdivision-specific holidays, expanding to more years and multi-language support. Hope it will be useful for you.
Unless otherwise noted all images are by the author