Beginner
Determining the current date is a public holiday can be tricky when holidays change and it of course changes from country to country. From system time of servers & machines running to timestamps for tracking the transactions and events in e-commerce platforms, the date and time play a major role. There are a variety of use cases related to manipulating date and time that can be solved using the inbuilt datetime module in Python3, such as
- Finding if a given year is a leap year or an ordinary year
- Finding the number of days between the two mentioned dates
- Convert between different date or time formats
What if you were to check if a given date is a public holiday? There isn’t any specific formula or logic to determine that, do we? Holidays can be pre-defined or uncalled for.
Here, we will be exploring the two ways to detect if a date is a holiday or not.
Checking For Public Holiday With Holidays Module
Although Python3 doesn’t provide any modules to detect if a date is a holiday or not, there are some of the external modules that help in detecting this. One of those modules is Holidays.
In your terminal, type in the following to get the module installed.
sudo pip3 install holidays

Now that our module is ready, let’s understand a bit about what the module and what it is capable of. Have a look at the following code snippet.
'''
Snippet to check if a given date is a holiday
'''
from datetime import date # Step 1
import holidays
us_holidays = holidays.UnitedStates() # Step 2
input_date = input("Enter the date as YYYY-MM-DD: ") # Step 3
holiday_name = us_holidays .get(input_date) # Step 4
if holiday_name != None:
output = "{} is a US Holiday - It's {}".format(input_date, holiday_name)
else:
output = "{} is not a US Holiday".format(input_date)
# Step 5
print (output)
In the above snippet,
- Step 1: Imports the required modules
- Step 2: Initializes the us_holidays object, so that the corresponding
getfunction can be invoked at step 3 - Step 3: Gets
dateinput from the user - Step 4: Invokes the get function of the
holidaysmodule. This returns the name of the holiday if the date is a holiday or returnsNonein case if it isn’t. This gets assigned to the variable –holiday_name. - Step 5: Based on the variable –
holiday_name, using theifclause the string formatting is done. Can you make this if clause even leaner? Read this article to know about the One line if else statements.
Here’s what the output looks like.

Checking For Holidays With API Call to Calendarific
The above method is suitable for simple projects; however, it can never be used to provide an enterprise-grade solution. Let’s say, you are building a web application for a holiday and travel startup, building an enterprise-grade application requires an enterprise-grade solution. If you haven’t noticed, the holidays module is pretty simple and if you consider state-wise or newly announced holidays, then this solution doesn’t simply cut for a large-scale application.
Enterprise requirements such as these can be satisfied by using external APIs such as Calendarific which provides the API as a service for such applications to consume. They keep updating the holidays of states and countries constantly, and the applications may consume these APIs. Of course, enterprise solutions don’t always come free, but the developer account has a limit of 1000API requests per month.
Locate to https://calendarific.com/ on your favorite browser and follow the steps as shown in the following images to get yourself a free account and an API key for this exercise.




Understanding the Calendarific REST API
Before we could dive into using the API KEY, get yourself a REST API client – Insomnia or Postman. We are about to test our API key if we are able to retrieve the holiday information. Plugin the following URL by replacing [APIKEY] text with your API KEY received from above on your REST client.
https://calendarific.com/api/v2/holidays?api_key=[APIKEY]&country=us-ny&type=national&year=2020&month=1&day=1
In the above URL:
- https://calendarific.com/api/v2 is the API Base URL
- /holidays is the API route
- api_key, country, type, year, month, day are URL Parameters
- Each parameter has a value allocated to it with an = (equal sign)
- Each parameter and value pair is split by an & (ampersand)
For the above API call, the following response will be received; the value corresponding to the code key under the meta tag as ‘200’ corresponds to a successful response.
{
"meta": {
"code": 200
},
"response": {
"holidays": [
{
"name": "New Year's Day",
"description": "New Year's Day is the first day of the Gregorian calendar, which is widely used in many countries such as the USA.",
"country": {
"id": "us",
"name": "United States"
},
"date": {
"iso": "2020-01-01",
"datetime": {
"year": 2020,
"month": 1,
"day": 1
}
},
"type": [
"National holiday"
],
"locations": "All",
"states": "All"
}
]
}
}
The REST API call has returned some useful info about the National holiday on the 1st of January. Let’s see if it’s able to detect for the 2nd of January. Plugin the following URL again by replacing the text [APIKEY] with your API Key.
https://calendarific.com/api/v2/holidays?api_key=[APIKEY]&country=us-ny&type=national&year=2020&month=1&day=2
The above URL should be returning a response similar to below.
{
"meta": {
"code": 200
},
"response": {
"holidays": []
}
}
Indeed, the 2nd of January is not a public holiday and hence, the holidays list inside the response nested JSON key turns out to be an empty list.
Now we know that our API works very well, it is now time to incorporate Calendarific REST API into our Python code. We will be using the requests module in order to make this happen. Here’s how it is done.
'''
Snippet to check if a given date is a holiday using an external API - Calendarific
'''
import requests # Step 1
api_key = '[APIKEY]' # Step 2
base_url = 'https://calendarific.com/api/v2'
api_route = '/holidays'
location = input("Enter Country & State code - E.g.: us-ny: ")
date_inpt = input("Enter the date as YYYY-MM-DD: ") # Step 3
y, m, d = date_inpt.split('-')
full_url = '{}{}?api_key={}&country={}&type=national&year={}&month={}&day={}'\
.format(base_url, api_route, api_key, location, str(int(y)), str(int(m)), str(int(d))) # Step 4
response = requests.get(full_url).json() # Step 5
if response['response']['holidays'] != []:
print ("{} is a holiday - {}".format(date_inpt, response['response']['holidays'][0]['name']))
else: # Step 6
print ("{} is not a holiday".format(date_inpt))
In the above snippet,
- Step 1: Import requests module – you will be needing this module to invoke the REST API.
- Step 2: Replace ‘[APIKEY]’ with your own API key from Calendarific
- Step 3: The user inputs the corresponding location and date for which the holiday needs to be detected
- Step 4: String formatting in order to frame the URL
- Step 5: Invoke the API and convert the response to a JSON; i.e.) a dictionary
- Step 6: If clause checks for the presence of an empty list or with a returned response.
Here’s what the output looks like.

And there you have it, a working example for detecting if a given date is a holiday using an external API.
Summary
From an overall perspective, there could be multiple ways to solve a given problem, and here, we have portrayed two of those ways in detecting if a given date is a holiday or not. One is a straight forward out-of-the-box solution and the other one is an enterprise-ready solution, which one would you choose?
Subscribe to our newsletter
How To Use Python alive-progress for Animated Progress Bars
Beginner
You have a data pipeline, a batch file processor, or a training loop that takes minutes to run. You stare at a blank terminal, no idea if it is working, 10% done, or silently crashed three minutes ago. The standard print(f'Processing {i}/{total}') approach floods the screen with output that doesn’t actually tell you the most important thing: how much time is left.
alive-progress is an animated terminal progress bar library that gives you real-time throughput stats, a live spinner, estimated time remaining, and configurable visual styles — all in a few lines of code. It goes well beyond tqdm in terms of visual richness and customization without sacrificing simplicity.
This article covers everything: basic usage with an iterable, the context manager API for manual advancement, customizing bar styles and themes, printing messages while a bar runs, nested bars for multi-stage pipelines, and a real-world batch image processor. After reading this, your terminal scripts will never look like they are frozen again.
Your First Progress Bar: Quick Example
The simplest way to use alive-progress is to wrap your iterable in alive_it() — a drop-in replacement for any for loop:
# quick_bar.py
from alive_progress import alive_it
import time
items = list(range(50))
for item in alive_it(items, title='Processing items'):
time.sleep(0.05) # Simulate work
print('Done!')
Terminal output (animated — static representation):
Processing items |████████████████████████| 50/50 [100%] in 2.6s (19.2/s)
alive_it() wraps any iterable and displays a real-time animated bar as you iterate. It shows the count, percentage, elapsed time, and throughput (items per second). When the loop finishes, the bar snaps to a clean static line showing the final stats. No extra code, no boilerplate — just wrap your existing loop.
What Is alive-progress?
alive-progress is a Python terminal progress bar library focused on visual quality and real-time statistics. Unlike simpler libraries, it uses ANSI escape codes to update the same terminal line in-place rather than printing new lines, creating a true animation effect that looks modern even on basic terminals.
| Feature | alive-progress | tqdm |
|---|---|---|
| Animated spinner | Yes (many styles) | Limited |
| Real-time throughput | Yes (ETA + rate) | Yes |
| Print while running | Yes (bar.text()) | Yes (tqdm.write()) |
| Nested bars | Yes | Yes |
| Custom bar styles | 40+ themes built-in | Limited |
| Unknown total | Yes (spinner mode) | Yes |
| Jupyter notebook | Limited | Better |
Install with: pip install alive-progress. Python 3.6+ is supported. No heavy dependencies — the only requirement is about-time, which is automatically installed.
Context Manager API
When your processing is not a simple loop — for example, you fetch items from an API in batches, or in you update at irregular intervals — use the context manager API with alive_bar() and manually call bar() to advance the counter.
# context_bar.py
from alive_progress import alive_bar
import time
total_files = 40
with alive_bar(total_files, title='Compressing files') as bar:
for i in range(total_files):
time.sleep(0.04) # Simulate compression
bar() # Advance the bar by 1
print('All files compressed!')
Output (static):
Compressing files |████████████████████████| 40/40 [100%] in 1.7s (23.5/s)
You can also advance by more than 1 at a time: bar(5) increments the counter by 5. This is useful when processing variable-size batches where the work unit is a batch, not an individual item. Pass None as the total to create an indeterminate bar that spins without a percentage — appropriate when you do not know the total count upfront.
Printing Messages While the Bar Runs
One of the most useful features is the ability to print status messages without disrupting the animated bar. Use bar.text() to show a live subtitle below the bar, or print() within the context — alive-progress intercepts it and displays it above the bar without breaking the animation.
# bar_messages.py
from alive_progress import alive_bar
import time
files = ['config.yaml', 'data.csv', 'model.pkl', 'report.html', 'archive.zip']
with alive_bar(len(files), title='Uploading') as bar:
for filename in files:
bar.text(f'-> {filename}') # Live subtitle
time.sleep(0.3)
size_kb = len(filename) * 100 # Fake file size
print(f'Uploaded {filename} ({size_kb} KB)')
bar()
Terminal output (sequential):
Uploaded config.yaml (1000 KB)
Uploaded data.csv (800 KB)
Uploaded model.pkl (900 KB)
Uploaded report.html (1100 KB)
Uploaded archive.zip (1000 KB)
Uploading |████████████████████████| 5/5 [100%] in 1.5s (3.3/s)
bar.text() updates the subtitle line in real time — so while the bar is animating, users see which file is currently uploading. When the loop completes, the printed lines stay in the terminal history above the final bar summary. This pattern is ideal for batch jobs where users want to know both overall progress and current activity.
Customizing Bar Styles
alive-progress ships with dozens of built-in bar and spinner styles. You can preview them all with alive_progress.styles.showtime(), or set them directly in the alive_bar() call or via the global config_handler.
# custom_styles.py
from alive_progress import alive_bar, config_handler
import time
# Set global defaults for all bars in this session
config_handler.set_global(
bar='classic',
spinner='classic',
title_length=20,
length=40
)
items = list(range(30))
with alive_bar(len(items), title='Classic bar') as bar:
for _ in items:
time.sleep(0.02)
bar()
# Override per-bar
with alive_bar(len(items), title='Smooth bar', bar='smooth', spinner='waves') as bar:
for _ in items:
time.sleep(0.02)
bar()
# Force mode -- for scripts called from CI/CD without a TTY
with alive_bar(len(items), title='Force mode', force_tty=True) as bar:
for _ in items:
time.sleep(0.02)
bar()
Output (static representations):
Classic bar [######################################] 30/30 in 0.7s
Smooth bar |████████████████████████████████████| 30/30 in 0.7s
Force mode |████████████████████████████████████| 30/30 in 0.7s
The config_handler.set_global() call persists across all subsequent bars in the same Python process — useful for setting a house style once at the start of a script. Individual bars can override global settings by passing keyword arguments. The force_tty=True argument is important for CI/CD pipelines: without it, alive-progress detects a non-interactive terminal and falls back to plain print output, which can look garbled in logs.
Unknown Total: Spinner Mode
Sometimes you do not know how many items there are before you start — streaming API responses, crawling a website, or reading from a queue. Pass None as the total and alive-progress switches to an indefinite spinner with a live count.
# spinner_mode.py
from alive_progress import alive_bar
import time
def stream_events():
# Simulate variable-length stream
for i in range(25):
time.sleep(0.08)
yield {'event': f'event_{i}', 'value': i * 3}
collected = []
with alive_bar(None, title='Streaming events') as bar:
for event in stream_events():
collected.append(event)
bar.text(f'Last: {event["event"]}')
bar()
print(f'Collected {len(collected)} events')
Output:
Streaming events |/| 25 in 2.1s (12.0/s)
Collected 25 events
In spinner mode the bar shows a rotating character instead of a fill animation, along with a raw count and throughput rate. This gives users feedback that the script is alive and working even when the end point is unknown.
Real-Life Example: Batch CSV Processor
# batch_csv_processor.py
import csv
import io
import time
from alive_progress import alive_bar
# Generate fake CSV data in memory
def make_fake_csv(n_rows):
buf = io.StringIO()
writer = csv.DictWriter(buf, fieldnames=['id', 'name', 'score', 'active'])
writer.writeheader()
for i in range(n_rows):
writer.writerow({
'id': i + 1,
'name': f'user_{i+1}',
'score': (i * 37) % 100,
'active': i % 3 != 0
})
buf.seek(0)
return buf
def process_csv(file_obj, bar, report):
reader = csv.DictReader(file_obj)
rows_processed = 0
for row in reader:
# Simulate row-level processing
time.sleep(0.002)
if row['active'] == 'True' and int(row['score']) > 50:
report['high_scorers'] += 1
rows_processed += 1
bar()
return rows_processed
# Simulate 4 CSV files of varying size
file_sizes = [80, 120, 60, 100]
file_names = [f'batch_{i+1}.csv' for i in range(len(file_sizes))]
report = {'high_scorers': 0, 'total_rows': 0}
total_rows = sum(file_sizes)
with alive_bar(total_rows, title='Processing batches') as bar:
for name, size in zip(file_names, file_sizes):
bar.text(f'-> {name}')
fake_csv = make_fake_csv(size)
rows = process_csv(fake_csv, bar, report)
report['total_rows'] += rows
print(f' Finished {name}: {rows} rows')
print(f'\nSummary: {report["total_rows"]} rows, {report["high_scorers"]} high scorers')
Output:
Finished batch_1.csv: 80 rows
Finished batch_2.csv: 120 rows
Finished batch_3.csv: 60 rows
Finished batch_4.csv: 100 rows
Processing batches |████████████████████████| 360/360 [100%] in 0.8s (450.0/s)
Summary: 360 rows, 48 high scorers
The bar advances once per row, so the animation accurately reflects actual progress across all files. bar.text() shows which file is being processed in real time. The print() calls inside the loop appear above the bar without disrupting the animation. Adapt this pattern to process real CSV files from disk, replace the fake data with csv.DictReader(open(path)), and add error handling for malformed rows.
Frequently Asked Questions
When should I use alive-progress vs tqdm?
Use alive-progress when you want richer animations and real-time text messages in interactive terminal scripts. Use tqdm when you need Jupyter notebook integration, pandas integration (df.progress_apply()), or when you are in a CI environment where animation is less important. Both are excellent; the choice is largely aesthetic.
alive-progress looks broken in my CI/CD logs. Why?
In non-TTY environments (CI logs, redirected output), alive-progress falls back to plain output by default, which can include ANSI escape codes that look garbled in log viewers. Fix it by adding force_tty=False to suppress the bar entirely in CI, or use enrich_print=False to prevent print interception. Alternatively, wrap the bar call in a check: if sys.stdout.isatty():.
What happens if I call bar() more times than the total?
The bar will exceed 100% — it does not hard-cap at the total. This is actually useful for cases where your estimate was wrong. If you need a strict cap, track the count manually and stop calling bar() when you reach the expected total. The final stats line will show the actual count regardless.
Does alive-progress work with multithreaded code?
Yes. The bar() call is thread-safe. You can call it from multiple threads concurrently and the counter will advance correctly. The display is updated in the main thread, so the animation stays smooth even under high call frequency from background threads.
What is the calibrate parameter?
The calibrate parameter controls the throughput animation speed. When your processing rate is very fast (thousands of items per second), the animation might update faster than it renders smoothly. Passing calibrate=100 tells alive-progress to treat 100 items/s as “full speed” for animation purposes, slowing down the visual effect to look natural. For most use cases the default auto-calibration works fine.
Conclusion
You have covered the full alive-progress toolkit: alive_it() for simple loop wrapping, the context manager alive_bar() for manual advancement, bar.text() for live subtitles, global and per-bar style configuration with config_handler, spinner mode for unknown totals, and the batch CSV example that ties everything together.
The best immediate use of this library is replacing any long-running script that currently outputs nothing — data import jobs, batch API calls, file processing pipelines — with a single with alive_bar(total) as bar: wrapper. The visual feedback improves debugging and gives users confidence that the script is working. Extend the CSV processor by adding a second nested bar for per-row sub-tasks, or integrate it with asyncio for parallel batch processing.
Browse all available styles with from alive_progress.styles import showtime; showtime(), and find the full documentation at github.com/rsalmei/alive-progress.
Related Articles
Further Reading: For more details, see the Python datetime module documentation.
Pro Tips for Working with Public Holidays in Python
1. Cache Holiday Data to Avoid Repeated API Calls
If you are using the Calendarific API, cache the results locally instead of calling the API every time you check a date. Holiday lists for a given country and year rarely change. Save the API response to a JSON file and only refresh it when the year changes. This reduces API usage and makes your application faster.
# cache_holidays.py
import json
import os
from datetime import date
CACHE_FILE = "holidays_cache.json"
def get_cached_holidays(country, year):
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE, "r") as f:
cache = json.load(f)
key = f"{country}_{year}"
if key in cache:
print(f"Using cached holidays for {country} {year}")
return cache[key]
return None
def save_to_cache(country, year, holidays):
cache = {}
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE, "r") as f:
cache = json.load(f)
cache[f"{country}_{year}"] = holidays
with open(CACHE_FILE, "w") as f:
json.dump(cache, f, indent=2)
print(f"Cached {len(holidays)} holidays for {country} {year}")
Output:
Cached 11 holidays for US 2026
Using cached holidays for US 2026
2. Calculate Business Days Excluding Holidays
One of the most common real-world uses of holiday detection is calculating business days. Combine the holidays library with Python’s datetime to count only working days between two dates, excluding weekends and public holidays. This is essential for shipping estimates, SLA calculations, and payroll processing.
# business_days.py
import holidays
from datetime import date, timedelta
def business_days_between(start, end, country="US"):
us_holidays = holidays.country_holidays(country)
count = 0
current = start
while current <= end:
if current.weekday() < 5 and current not in us_holidays:
count += 1
current += timedelta(days=1)
return count
start = date(2026, 12, 20)
end = date(2026, 12, 31)
days = business_days_between(start, end)
print(f"Business days from {start} to {end}: {days}")
Output:
Business days from 2026-12-20 to 2026-12-31: 7
3. Handle Multiple Countries for International Apps
If your application serves users in different countries, check holidays for each user's country rather than assuming a single country. The holidays library supports 100+ countries. Store each user's country code and pass it when checking holidays. Remember that some countries have regional holidays too -- for example, different states in Australia or provinces in Canada have different public holidays.
4. Build a Holiday-Aware Scheduler
Many applications need to skip processing on holidays. Instead of checking manually every time, create a decorator that wraps scheduled tasks and automatically skips execution on public holidays. This is useful for automated reports, email campaigns, and batch processing jobs that should only run on business days.
# holiday_aware_scheduler.py
import holidays
from datetime import date
from functools import wraps
def skip_on_holidays(country="US"):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
today = date.today()
if today in holidays.country_holidays(country):
name = holidays.country_holidays(country).get(today)
print(f"Skipping {func.__name__}: today is {name}")
return None
return func(*args, **kwargs)
return wrapper
return decorator
@skip_on_holidays("US")
def send_daily_report():
print("Sending daily report...")
return "Report sent"
result = send_daily_report()
print(f"Result: {result}")
Output (on a regular business day):
Sending daily report...
Result: Report sent
5. Display Upcoming Holidays for Better UX
Show your users which holidays are coming up so they can plan ahead. This is valuable for project management tools, delivery estimate pages, and HR applications. Sort the holiday list by date and filter for upcoming dates only to give users a clear view of the next few holidays.
Frequently Asked Questions
How do I check if a date is a public holiday in Python?
Use the holidays library: install it with pip install holidays, then check with date in holidays.country_holidays('US'). It returns True if the date is a recognized public holiday for that country.
What countries does the Python holidays library support?
The holidays library supports over 100 countries and their subdivisions. Major countries include the US, UK, Canada, Australia, Germany, France, India, and many more. Use holidays.list_supported_countries() to see the complete list.
Can I add custom holidays to the holidays library?
Yes. Create a custom holiday class inheriting from the country class, or use the append() method to add individual dates. You can also create entirely custom holiday calendars for company-specific or regional holidays.
How do I get the name of a holiday for a specific date?
Access the holiday name with holidays.country_holidays('US').get(date), which returns the holiday name as a string, or None if it is not a holiday. You can also iterate over the holidays object to list all holidays in a year.
Is the holidays library useful for business day calculations?
Yes. Combine it with numpy.busday_count() or pandas.bdate_range() to calculate working days excluding public holidays. This is useful for project management, payroll calculations, and delivery date estimation.