Building an SLA Deadline Tracker in Python
"Respond within 24 business hours." Every support team has this commitment. It sounds like a simple timer. In practice, it's one of the most reliably buggy pieces of logic in a ticketing system.
Let's look at what "24 business hours" actually means, why naive implementations fail, and how to build a CLI tool that gets it right.
What "24 Business Hours" Actually Means
If a ticket arrives at 3pm on a Friday, "24 business hours" does not mean Saturday at 3pm. It means:
- Friday 3pm–5pm = 2 hours
- Monday 9am–5pm = 8 hours (2 + 8 = 10)
- Tuesday 9am–5pm = 8 hours (10 + 8 = 18)
- Wednesday 9am–3pm = 6 hours (18 + 6 = 24)
- Deadline: Wednesday 3pm
That's a full 5 calendar days after the ticket opened. Any system that just adds timedelta(hours=24) to the open time will set the deadline wrong.
Now add DST. The US switches from EST (UTC-5) to EDT (UTC-4) in March. A ticket opened at 9am on the Sunday before the switch, carrying an SLA through Monday, will have an off-by-one-hour error if your code doesn't handle the transition properly.
The Naive Approach
from datetime import datetime, timedelta
def sla_deadline(opened_at: datetime, sla_hours: int) -> datetime:
return opened_at + timedelta(hours=sla_hours)
# Ticket opened Friday 3pm
opened = datetime(2024, 3, 15, 15, 0)
print(sla_deadline(opened, 24))
# → 2024-03-16 15:00:00 (Saturday 3pm — wrong)
Some teams add a weekend check:
def sla_deadline_v2(opened_at: datetime, sla_hours: int) -> datetime:
current = opened_at
remaining = sla_hours
while remaining > 0:
current += timedelta(hours=1)
if current.weekday() < 5: # Monday=0, Friday=4
remaining -= 1
return current
This is better but still wrong:
- It counts all hours on weekdays, not just business hours (9am–5pm)
- It ignores holidays
- It doesn't account for DST
The Right Tool: BizCal API's /working-hours/deadline
BizCal API has a dedicated endpoint for this: GET /working-hours/deadline.
You pass it:
-
start— the datetime when the SLA clock started (ISO 8601) -
sla_hours— the number of working hours in the SLA -
timezone— IANA timezone string (e.g.America/New_York) -
country— for holiday exclusion
import requests
r = requests.get(
"https://bizcalapi.com/working-hours/deadline",
params={
"start": "2024-03-15T15:00:00",
"sla_hours": 24,
"timezone": "America/New_York",
"country": "US",
},
headers={
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "bizcalapi.com",
},
)
print(r.json()["deadline"])
# → "2024-03-20T15:00:00-04:00" (Wednesday 3pm, correctly in EDT after DST)
The response comes back in the correct offset (-04:00 for EDT) — the API handled the DST transition.
Building the CLI Tool
#!/usr/bin/env python3
"""
sla.py — Calculate SLA deadlines in business hours.
Usage:
python sla.py --start "2024-03-15T15:00:00" --hours 24 --tz "America/New_York" --country US
"""
import argparse
import sys
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://bizcalapi.com"
HEADERS = {
"X-RapidAPI-Key": API_KEY,
"X-RapidAPI-Host": "bizcalapi.com",
}
def calculate_deadline(start: str, sla_hours: int, timezone: str, country: str) -> dict:
r = requests.get(
f"{BASE_URL}/working-hours/deadline",
params={"start": start, "sla_hours": sla_hours, "timezone": timezone, "country": country},
headers=HEADERS,
timeout=10,
)
r.raise_for_status()
return r.json()
def format_output(result: dict) -> str:
lines = [
f"SLA Start: {result['start']}",
f"SLA Hours: {result['sla_hours']}",
f"Deadline: {result['deadline']}",
f"Timezone: {result['timezone']}",
f"Country: {result['country']}",
]
if result.get("holidays_excluded"):
lines.append(f"Holidays excluded: {', '.join(result['holidays_excluded'])}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--start", required=True)
parser.add_argument("--hours", type=int, required=True)
parser.add_argument("--tz", default="UTC")
parser.add_argument("--country", default="US")
args = parser.parse_args()
try:
result = calculate_deadline(args.start, args.hours, args.tz, args.country)
print(format_output(result))
except requests.HTTPError as e:
print(f"API error: {e.response.status_code}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Run it:
python sla.py --start "2024-03-15T15:00:00" --hours 24 --tz "America/New_York" --country US
# SLA Start: 2024-03-15T15:00:00
# SLA Hours: 24
# Deadline: 2024-03-20T15:00:00-04:00
# Timezone: America/New_York
# Country: US
python sla.py --start "2024-12-24T10:00:00" --hours 8 --tz "Europe/London" --country GB
# Deadline: 2024-12-27T10:00:00+00:00
# Holidays excluded: Christmas Day, Boxing Day
The DST Case That Catches Everyone
# Ticket opened Friday March 8 at 4pm EST
# 8-hour SLA carries over the weekend + through DST switch (March 10)
# Naive: Monday March 11 at 4pm EST (timedelta over business hours)
# Correct: Monday March 11 at 4pm EDT (UTC-4, not UTC-5)
The API uses Python's zoneinfo module under the hood, which handles IANA timezone rules correctly across DST transitions. Always pass full IANA strings like America/New_York — never fixed-offset strings like EST.
Integrating Into a Ticket System
def set_sla_deadline(ticket: dict, sla_hours: int, country: str, timezone: str) -> str:
result = calculate_deadline(
start=ticket["created_at"],
sla_hours=sla_hours,
timezone=timezone,
country=country,
)
return result["deadline"]
Store the deadline in your ticket record. Compare datetime.now(tz) against the parsed deadline on each status check. No custom calendar logic in your codebase.
Get Started
Subscribe on RapidAPI: BizCal API
Free tier: 50 requests/day. Basic is $9.99/mo for 1,000 requests/day across all 15 countries.
Top comments (0)