Last Updated: June 01, 2026
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.
Python developer and educator with 15+ years building production systems across data engineering, web APIs, and AI tooling. Founder of Python How To Program — 270+ in-depth tutorials covering the modern Python stack.
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 Get CPU Core Usage with psutil in Python
Intermediate
Your server is running slow, but top shows average CPU at 45% — nothing alarming. Then a colleague points out that core 3 has been pinned at 100% for the last hour while the other seven cores sit idle. A single-threaded bottleneck is strangling your app, invisible to anyone watching only the aggregate number. This is exactly the kind of problem you cannot catch without per-core monitoring, and Python makes it surprisingly easy to build.
The psutil library gives you cross-platform access to CPU usage per core, per-core clock frequency, per-core time breakdowns (user, system, idle), and memory statistics — all in a few lines of Python. It works identically on Windows, macOS, and Linux without requiring root access or system-specific tools like top, htop, or Task Manager. Install it once with pip and you are ready to go.
In this article we will cover everything you need to build a CPU monitoring tool with psutil. We start with a Quick Example so you get per-core numbers immediately. Then we dig into cpu_percent(), physical vs logical core counts, per-core frequency with cpu_freq(), time breakdowns with cpu_times(), memory monitoring, and threshold-based alerting. By the end you will have a real-time terminal dashboard you can point at any machine.
Getting Per-Core CPU Usage: Quick Example
Let us start with the most useful function in psutil for this task. The key is the percpu=True flag on cpu_percent() — without it you get one aggregate number; with it you get a list of percentages, one per logical core.
# quick_cpu_check.py
import psutil
import time
# Pass interval=1 to measure over a 1-second window (recommended)
# percpu=True returns a list -- one value per logical CPU core
core_usage = psutil.cpu_percent(interval=1, percpu=True)
print(f"Logical cores detected: {len(core_usage)}")
print()
for i, pct in enumerate(core_usage):
bar = "#" * int(pct / 5)
print(f" Core {i:>2}: {pct:5.1f}% [{bar:<20}]")
print()
print(f" Overall: {psutil.cpu_percent(interval=None):.1f}%")
Output:
Logical cores detected: 8
Core 0: 23.4% [#### ]
Core 1: 8.1% [# ]
Core 2: 91.3% [################## ]
Core 3: 6.2% [# ]
Core 4: 12.7% [## ]
Core 5: 9.4% [# ]
Core 6: 17.6% [### ]
Core 7: 5.0% [# ]
Overall: 21.7%
The output instantly reveals that core 2 is at 91% while the overall average looks benign at 21.7%. That discrepancy is exactly what aggregate monitoring misses. The interval=1 parameter tells psutil to collect a sample, wait one second, collect another, and return the difference -- this gives you a meaningful measurement rather than a snapshot that could be zero. The len(core_usage) check tells you how many logical cores the machine has, which varies from 2 on a budget laptop to 128 on a high-end server.
The rest of this article explains how each piece works, adds frequency and memory data, and builds toward a live refreshing terminal dashboard. Read on for the details, or jump straight to the Real-Life Example if you want the full script now.
What is psutil and Why Use It?
psutil (process and system utilities) is a cross-platform library for retrieving information on running processes and system utilization -- CPU, memory, disks, network, and sensors. It wraps the underlying OS interfaces (/proc on Linux, sysctl on macOS, Win32 API on Windows) so your Python code runs unchanged on all three platforms.
The alternative to psutil is platform-specific shell commands: mpstat -P ALL 1 on Linux, sysctl hw.perflevel0.physicalcpu on macOS, or WMI queries on Windows. You could parse their output with subprocess, but you would need separate code paths for each OS and your script would break every time the command output format changes. psutil solves all of that.
| Method | Platform | Root Required | Per-Core Data | Python API |
|---|---|---|---|---|
| psutil | Windows / macOS / Linux | No | Yes | Yes -- clean objects |
| mpstat | Linux only | No | Yes | Parse subprocess output |
| top / htop | Unix-like | No | Yes | No -- interactive only |
| WMI | Windows only | Admin for some | Partial | Via pywin32 |
| /proc/stat | Linux only | No | Yes | Manual file parsing |
Install psutil with pip -- it has no dependencies and compiles quickly:
# install_psutil.sh
pip install psutil
Once installed you can import it and immediately start querying system metrics. The sections below walk through each function you need for CPU monitoring.
Logical vs Physical Cores: What cpu_count() Returns
Before diving deeper into usage numbers, it helps to understand what "core" actually means here. Modern CPUs expose more logical cores than they have physical cores because of hyperthreading (Intel) or SMT (AMD). A 4-core chip with hyperthreading shows up as 8 logical cores. psutil lets you query both counts.
# core_count.py
import psutil
logical = psutil.cpu_count(logical=True) # includes hyperthreads
physical = psutil.cpu_count(logical=False) # physical cores only
print(f"Physical cores: {physical}")
print(f"Logical cores: {logical}")
print(f"Hyperthreading: {'Yes' if logical > physical else 'No'}")
print(f"HT ratio: {logical // physical}x" if physical else "")
Output:
Physical cores: 4
Logical cores: 8
Hyperthreading: Yes
HT ratio: 2x
The number of items in the list returned by cpu_percent(percpu=True) always matches cpu_count(logical=True) -- you get one entry per logical core. Physical core count matters for workloads that benefit from true parallelism (CPU-bound Python processes, for example) vs workloads that are mostly I/O-bound and can share a core fine. Knowing the physical count also helps you interpret the per-core usage: if logical cores 0 and 1 are both busy, that is likely one physical core under full load.
Per-Core Frequency with cpu_freq()
CPU frequency tells you whether a core is running at full speed or has been throttled by thermal limits. Modern processors use dynamic frequency scaling: they boost above the rated speed when the workload demands it (and the chip is cool enough), and throttle down to save power or prevent overheating.
# cpu_frequency.py
import psutil
# percpu=True returns a list of scpufreq namedtuples
freqs = psutil.cpu_freq(percpu=True)
if freqs:
print(f"{'Core':<8} {'Current MHz':>12} {'Min MHz':>10} {'Max MHz':>10}")
print("-" * 44)
for i, f in enumerate(freqs):
print(f"Core {i:<3} {f.current:>10.0f} {f.min:>9.0f} {f.max:>9.0f}")
else:
# Some Linux VMs do not expose per-core frequency
overall = psutil.cpu_freq()
print(f"Per-core freq not available. Overall: {overall.current:.0f} MHz")
Output:
Core Current MHz Min MHz Max MHz
--------------------------------------------
Core 0 3600 800 4200
Core 1 4100 800 4200
Core 2 4200 800 4200
Core 3 3200 800 4200
Core 4 3800 800 4200
Core 5 4000 800 4200
Core 6 4200 800 4200
Core 7 2900 800 4200
A core sitting at its maximum frequency (4200 MHz here) that also shows high CPU usage is healthy -- it is working hard and boosting as designed. A core showing high CPU usage but stuck at minimum frequency (800 MHz) is likely being throttled due to heat, and you have a cooling problem rather than a workload problem. The defensive check for if freqs: is important: some virtualized Linux environments do not expose per-core frequency and return an empty list.
Per-Core Time Breakdown with cpu_times()
CPU usage percentage tells you HOW MUCH a core is working, but not what it is doing. cpu_times() breaks the time a CPU has spent into categories: user space (your code), kernel space (system calls), idle, and on Linux you also get I/O wait and steal time (from hypervisor overhead in VMs).
# cpu_times_breakdown.py
import psutil
times = psutil.cpu_times(percpu=True)
print(f"{'Core':<6} {'User%':>7} {'Sys%':>7} {'Idle%':>7} {'IOWait%':>9}")
print("-" * 40)
for i, t in enumerate(times):
total = t.user + t.system + t.idle + getattr(t, 'iowait', 0.0)
if total == 0:
continue
user_pct = t.user / total * 100
sys_pct = t.system / total * 100
idle_pct = t.idle / total * 100
iowait_pct = getattr(t, 'iowait', 0.0) / total * 100
print(f"Core {i:<1} {user_pct:>7.1f} {sys_pct:>7.1f} {idle_pct:>7.1f} {iowait_pct:>9.1f}")
Output:
Core User% Sys% Idle% IOWait%
----------------------------------------
Core 0 18.2 4.1 77.7 0.0
Core 1 6.5 1.6 91.9 0.0
Core 2 88.4 2.9 8.7 0.0
Core 3 5.1 1.1 93.8 0.0
Core 4 11.3 0.8 87.9 0.1
Core 5 8.7 0.9 90.4 0.0
Core 6 15.2 1.4 83.4 0.0
Core 7 4.2 0.6 95.2 0.0
Note the getattr(t, 'iowait', 0.0) pattern. The iowait field only exists on Linux; using getattr with a default keeps the code portable to macOS and Windows. A core with high user% is running application code. High sys% means lots of system calls (file I/O, socket operations). High iowait% means the core is waiting on storage -- often a sign that your database or file access is the real bottleneck, not CPU.
Memory Monitoring: virtual_memory()
CPU monitoring is rarely useful in isolation -- memory pressure often causes CPU spikes as the OS spends cycles on swapping. Adding memory data to your monitor gives a more complete picture.
# memory_check.py
import psutil
mem = psutil.virtual_memory()
swap = psutil.swap_memory()
def fmt_bytes(n):
for unit in ('B', 'KB', 'MB', 'GB', 'TB'):
if n < 1024:
return f"{n:.1f} {unit}"
n /= 1024
return f"{n:.1f} PB"
print("RAM:")
print(f" Total: {fmt_bytes(mem.total)}")
print(f" Available: {fmt_bytes(mem.available)}")
print(f" Used: {fmt_bytes(mem.used)} ({mem.percent:.1f}%)")
print(f" Buffers: {fmt_bytes(getattr(mem, 'buffers', 0))}")
print(f" Cached: {fmt_bytes(getattr(mem, 'cached', 0))}")
print()
print("Swap:")
print(f" Total: {fmt_bytes(swap.total)}")
print(f" Used: {fmt_bytes(swap.used)} ({swap.percent:.1f}%)")
Output:
RAM:
Total: 15.9 GB
Available: 9.3 GB
Used: 6.1 GB (38.7%)
Buffers: 312.0 MB
Cached: 4.2 GB
Swap:
Total: 2.0 GB
Used: 0.0 MB (0.0%)
The mem.available field is the most actionable metric here -- it is not the same as mem.total - mem.used. Available includes memory that is currently used for caches but can be reclaimed immediately by applications. If mem.available drops near zero while swap.percent climbs, your machine is under genuine memory pressure and performance will degrade. The getattr calls on buffers and cached guard against Windows, which does not expose those fields.
Threshold Alerting: Raising Warnings When Cores Spike
Collecting metrics is only useful if something reacts to them. The next step is adding threshold checks so your monitoring code can trigger an alert, write to a log file, or send a notification when a core crosses a usage limit you define.
# cpu_alerts.py
import psutil
import time
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S",
)
CPU_WARN_PCT = 70.0 # warn if any single core exceeds this
CPU_CRIT_PCT = 90.0 # critical if any core exceeds this
MEM_WARN_PCT = 80.0 # warn if RAM usage exceeds this
CHECK_INTERVAL = 5 # seconds between checks
def check_once():
per_core = psutil.cpu_percent(interval=1, percpu=True)
mem = psutil.virtual_memory()
for i, pct in enumerate(per_core):
if pct >= CPU_CRIT_PCT:
logging.critical("Core %d at %.1f%% -- CRITICAL", i, pct)
elif pct >= CPU_WARN_PCT:
logging.warning("Core %d at %.1f%% -- high usage", i, pct)
if mem.percent >= MEM_WARN_PCT:
logging.warning("RAM at %.1f%% -- available: %.1f GB",
mem.percent, mem.available / 1e9)
if __name__ == "__main__":
logging.info("Starting CPU/memory monitor (Ctrl+C to stop)")
try:
while True:
check_once()
time.sleep(CHECK_INTERVAL)
except KeyboardInterrupt:
logging.info("Monitor stopped.")
Output:
09:14:01 [INFO] Starting CPU/memory monitor (Ctrl+C to stop)
09:14:02 [WARNING] Core 2 at 73.5% -- high usage
09:14:07 [CRITICAL] Core 2 at 94.1% -- CRITICAL
09:14:12 [CRITICAL] Core 2 at 98.7% -- CRITICAL
09:14:17 [INFO] Monitor stopped.
Using the standard logging module rather than print() means you can redirect this output to a file with one line change (filename="monitor.log" in the basicConfig call), or hook it into any structured logging pipeline. The CHECK_INTERVAL constant separated from cpu_percent(interval=1) is intentional -- the interval on cpu_percent controls measurement accuracy, while CHECK_INTERVAL controls how often you act on the results.
Real-Life Example: Live Terminal CPU Dashboard
Let us combine everything into a dashboard that refreshes in place every two seconds, showing per-core bars, frequency, and memory -- all in one compact terminal view.
# cpu_dashboard.py
import psutil
import time
import os
CPU_WARN = 70.0
CPU_CRIT = 90.0
REFRESH = 2.0 # seconds between refreshes
def color(pct):
"""Return ANSI color code based on usage percentage."""
if pct >= CPU_CRIT:
return "\033[91m" # bright red
if pct >= CPU_WARN:
return "\033[93m" # yellow
return "\033[92m" # green
RESET = "\033[0m"
def make_bar(pct, width=24):
filled = int(pct / 100 * width)
return "#" * filled + "-" * (width - filled)
def render():
os.system("cls" if os.name == "nt" else "clear")
print("=" * 56)
print(" psutil CPU Dashboard -- press Ctrl+C to exit")
print("=" * 56)
per_core = psutil.cpu_percent(interval=1, percpu=True)
freqs = psutil.cpu_freq(percpu=True) or []
mem = psutil.virtual_memory()
logical = psutil.cpu_count(logical=True)
physical = psutil.cpu_count(logical=False)
print(f" Cores: {physical} physical / {logical} logical\n")
for i, pct in enumerate(per_core):
freq_str = ""
if i < len(freqs):
freq_str = f" {freqs[i].current:>5.0f} MHz"
bar = make_bar(pct)
c = color(pct)
print(f" Core {i:>2}: {c}[{bar}]{RESET} {pct:5.1f}%{freq_str}")
avg = sum(per_core) / len(per_core) if per_core else 0
print(f"\n Avg: [{make_bar(avg)}] {avg:5.1f}%")
print()
mem_bar = make_bar(mem.percent, width=24)
mc = color(mem.percent)
avail_gb = mem.available / 1e9
print(f" RAM: {mc}[{mem_bar}]{RESET} {mem.percent:5.1f}% "
f"({avail_gb:.1f} GB free)")
swap = psutil.swap_memory()
if swap.total > 0:
swap_bar = make_bar(swap.percent, width=24)
sc = color(swap.percent)
print(f" Swap: {sc}[{swap_bar}]{RESET} {swap.percent:5.1f}%")
print()
print(f" Updated every {REFRESH}s -- {time.strftime('%H:%M:%S')}")
print("=" * 56)
if __name__ == "__main__":
try:
while True:
render()
time.sleep(REFRESH)
except KeyboardInterrupt:
print("\nDashboard stopped.")
Output (sample frame):
========================================================
psutil CPU Dashboard -- press Ctrl+C to exit
========================================================
Cores: 4 physical / 8 logical
Core 0: [######------------------] 25.4% 3600 MHz
Core 1: [#-----------------------] 8.1% 2900 MHz
Core 2: [######################--] 91.3% 4200 MHz
Core 3: [#-----------------------] 6.2% 3100 MHz
Core 4: [###---------------------] 12.7% 3400 MHz
Core 5: [##----------------------] 9.4% 3200 MHz
Core 6: [###---------------------] 17.6% 3800 MHz
Core 7: [#-----------------------] 5.0% 2800 MHz
Avg: [####--------------------] 22.0%
RAM: [############------------] 51.2% (7.8 GB free)
Swap: [------------------------] 0.0%
Updated every 2s -- 09:17:44
========================================================
The os.system("cls" if os.name == "nt" else "clear") call clears the terminal before each refresh, giving the appearance of an in-place update rather than scrolling output. The ANSI color codes turn critical cores red and high-usage cores yellow in any terminal that supports them (macOS Terminal, Linux terminals, Windows Terminal). To log to a file instead of the terminal, replace the render() call with the check_once() pattern from the alerting section. You can also extend this script by adding disk I/O stats with psutil.disk_io_counters(perdisk=True) or network throughput with psutil.net_io_counters(pernic=True).
Frequently Asked Questions
Why does cpu_percent() return 0.0 when I call it with no arguments?
The first call to psutil.cpu_percent() with no interval and no previous call in the same process always returns 0.0. psutil calculates CPU usage as the difference between two samples taken some time apart. The first call just sets the baseline; the second call (or a call with interval=N) returns the actual measurement. Always use interval=1 (or at least 0.1) for accurate readings, or call the function once at startup to prime it and then call it again after a small sleep.
When should I use logical=True vs logical=False in cpu_count()?
Use cpu_count(logical=True) when you want to know how many workers to create for I/O-bound tasks -- more logical cores means more threads can be useful. Use cpu_count(logical=False) for CPU-bound work where you spawn Python processes -- extra logical cores from hyperthreading rarely help CPU-bound code and can actually hurt throughput by competing for the same physical core resources. When in doubt, benchmark both: run your workload with physical workers and with logical workers and compare wall-clock time.
Does psutil need root/admin privileges?
No -- reading CPU usage percentages, frequencies, core counts, and memory stats does not require elevated permissions on Windows, macOS, or Linux. Some psutil functions DO require root, such as reading per-process memory maps or certain sensor temperatures (psutil.sensors_temperatures()). For a pure CPU and memory monitoring script like the one in this article, you can run as a regular user. If you get a psutil.AccessDenied exception, check which specific function triggered it -- it is almost certainly a process-level function, not a system-level one.
cpu_freq(percpu=True) returns an empty list on my Linux VM. What is wrong?
This is expected behavior on many virtualized Linux environments. The guest OS does not always have access to the host CPU's frequency scaling information. The psutil.cpu_freq() function reads from /sys/devices/system/cpu/cpu*/cpufreq/ on Linux, which may not be populated by the hypervisor. Some cloud VMs (AWS, GCP, Azure) intentionally withhold this data. The safe approach is to always check if freqs: before iterating, and fall back to a single aggregate call (psutil.cpu_freq(percpu=False)) or simply skip the frequency column. The CPU usage percentage from cpu_percent() remains accurate even when frequency data is unavailable.
Does this code work on Windows without any changes?
Yes, with one small caveat: the ANSI color codes in the dashboard script require Windows 10 version 1607 or later with Windows Terminal or a VT100-compatible terminal. The standard Windows Command Prompt (cmd.exe) on older Windows versions does not render ANSI codes and will display them as literal characters like [91m. You can guard against this by wrapping the ANSI output in a try/except or by using the colorama library (pip install colorama), which translates ANSI codes to Win32 console calls. Everything else -- cpu_percent(), cpu_count(), cpu_freq(), virtual_memory(), and swap_memory() -- works identically on Windows.
Can I get CPU temperature with psutil?
On Linux and some macOS hardware, yes: psutil.sensors_temperatures() returns a dictionary of sensor readings grouped by device name. The key for CPU cores is usually 'coretemp' or 'k10temp' depending on the chip. Each entry has current, high, and critical temperature values in Celsius. This function is not available on Windows -- psutil simply does not expose it there because the Windows thermal sensor APIs require platform-specific third-party libraries. On unsupported platforms the call raises AttributeError, so always check hasattr(psutil, 'sensors_temperatures') before using it.
Conclusion
psutil makes per-core CPU monitoring a matter of two function calls. cpu_percent(interval=1, percpu=True) gives you a list of usage values -- one per logical core -- that reveals the imbalances a single aggregate number would hide. cpu_count(logical=True/False) tells you whether extra cores come from hyperthreading or are genuine physical cores. cpu_freq(percpu=True) shows whether cores are boosting or being throttled. cpu_times(percpu=True) breaks usage down into user, system, and iowait time so you know whether CPU cycles are spent on application code, kernel calls, or waiting on storage. And virtual_memory() and swap_memory() round out the picture by capturing memory pressure alongside CPU load.
Extend the dashboard by adding psutil.disk_io_counters(perdisk=True) for storage throughput, psutil.net_io_counters(pernic=True) for network stats, or hook the alert thresholds into a notification service like Slack or PagerDuty. You could also export metrics to a time-series database like Prometheus by wrapping the psutil calls in a Flask endpoint and adding a Prometheus client. The psutil documentation at psutil.readthedocs.io covers every available function in depth.
For deeper exploration, the Python Scalene profiler article shows how to go beyond monitoring into detailed line-level CPU and memory profiling within your own code, and the Python task automation guide covers scheduling monitoring scripts to run on a cron job.
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.
Related Articles
- How To Use Python litellm for Multi-Model LLM Integration
- How To Use Python instructor for Structured LLM Outputs
- How To Use Python LlamaIndex for Document Q&A
- How To Use PyJWT for JSON Web Tokens in Python
- How To Use Python cryptography for Encryption and Decryption
- How To Build a Python gRPC Service with grpcio
Continue Learning Python
Tutorials you might also find useful: