DEV Community

alex
alex

Posted on Edited on

Every Holiday API Gets China Wrong - Here's How I Fixed It

\n*This post was updated on Sep 4, 2026 — the API now has dedicated Business Days endpoints (workday counting, next-business-day, monthly stats). Scroll to "What's New" below.*

Before every Chinese New Year, backend developer groups see the same question:

"Is February 14th a workday or not?"

The answer: Yes, it's a workday. It's a "调休补班" — a Saturday, but you have to go to the office.


The Problem

Mainstream holiday APIs (Calendarific, HolidayAPI.com, Abstract API, Nager.Date) all cover 200+ countries' public holidays. But they share one blind spot when it comes to China:

They only return official holidays. They don't handle adjusted workdays (调休补班).

China's 2026 holiday schedule (source: State Council notice, Nov 2025):

Holiday Days off Make-up workdays
New Year Jan 1–3 (3 days) Jan 4 (Sun)
Spring Festival Feb 15–23 (9 days) Feb 14 (Sat) & Feb 28 (Sat)
Labor Day May 1–5 (5 days) May 9 (Sat)
National Day Oct 1–7 (7 days) Sep 20 (Sun) & Oct 10 (Sat)

That means 2026 has 6 weekend days where you must go to work. If you query 2026-02-14 with any mainstream API:

// Other APIs return:
{ "date": "2026-02-14", "weekday": "Saturday", "type": "weekend" }
//  Wrong! This is an adjusted workday  you have to work

// This API returns:
{ "date": "2026-02-14", "weekday": "Saturday", "type": "adjusted_workday", "is_workday": true }
Enter fullscreen mode Exit fullscreen mode

If your app involves scheduling, attendance, order ETA, or cron logic — this error causes:

  • ❌ No staff scheduled on adjusted workdays → uncovered shifts
  • ❌ Attendance system marks employees "absent" on actual workdays
  • ❌ Order ETAs off by a day
  • ❌ Cron jobs fire (or don't) incorrectly

The Solution

I built the Business Days & Holidays API — 501 countries/regions, with China's adjusted workdays as exclusive data, sourced from official State Council notices and hand-verified.

🆕 What's New: Business Days Endpoints

Since the original post, I've added three endpoints purpose-built for the "count workdays" jobs this API's users kept asking about:

Endpoint What it does Typical use
GET /business-days-between/{country} Count workdays between two dates Payroll cycles, interest days, SLA
GET /next-business-day/{country} The Nth workday after a date Invoice due dates, T+N settlement
GET /business-days-in-month/{country} Monthly workday stats + full date list HR scheduling, payroll reports

Example — "invoice due in 3 business days" starting Sep 30, 2026 (Golden Week starts Oct 1):

{
  "from_date": "2026-09-30",
  "next_business_day": "2026-10-10",
  "weekday": "Saturday",
  "name": "调休补班",
  "calendar_days_taken": 10
}
Enter fullscreen mode Exit fullscreen mode

Oct 10 — a Saturday that is actually a workday. The API tells you why ("name": "调休补班").

Example — full-year workday count for 2026:

{
  "calendar_days": 365,
  "business_days": 248,
  "holidays": 33,
  "weekends": 84,
  "adjusted_workdays_included": 6
}
Enter fullscreen mode Exit fullscreen mode

248, not the naive ~261 — make-up days counted in, weekday holidays counted out.


Quick Start

cURL

curl "https://global-holidays-api3.p.rapidapi.com/is-workday/CN/2026-02-14" \
  -H "X-RapidAPI-Key: YOUR_KEY" \
  -H "X-RapidAPI-Host: global-holidays-api3.p.rapidapi.com"
Enter fullscreen mode Exit fullscreen mode

Python

import requests

headers = {
    "X-RapidAPI-Key": "YOUR_KEY",
    "X-RapidAPI-Host": "global-holidays-api3.p.rapidapi.com",
}
BASE = "https://global-holidays-api3.p.rapidapi.com"

# 1. Is Feb 14 a workday?
requests.get(f"{BASE}/is-workday/CN/2026-02-14", headers=headers).json()
# → {"type": "adjusted_workday", "is_workday": true, "name": "调休补班"}

# 2. All China make-up workdays in 2026
requests.get(f"{BASE}/china/adjusted-workdays/2026", headers=headers).json()
# → 6 dates: Jan 4, Feb 14, Feb 28, May 9, Sep 20, Oct 10

# 3. Workdays between two dates (payroll cycle)
requests.get(f"{BASE}/business-days-between/CN",
             headers=headers,
             params={"start": "2026-02-01", "end": "2026-02-28"}).json()
# → {"business_days": 16, "holidays": 9, "weekends": 3, "adjusted_workdays_included": 2}
Enter fullscreen mode Exit fullscreen mode

Real-World Scenarios

1. Attendance system — enable clock-in only on real workdays:

result = client.is_workday("CN", "2026-02-14")
if result["is_workday"]:
    enable_clock_in()
Enter fullscreen mode Exit fullscreen mode

2. Order ETA — count actual workdays, not calendar days:

result = client.business_days_between("CN", start="2026-02-12", end="2026-02-18")
# → workdays: 2 (Spring Festival week!)
Enter fullscreen mode Exit fullscreen mode

3. Cron jobs — skip holidays, run on make-up workdays:

if not client.is_workday("CN", "2026-02-16")["is_workday"]:
    skip()  # It's a holiday
Enter fullscreen mode Exit fullscreen mode

4. Invoice due dates — "due in 3 business days" done right:

result = client.next_business_day("CN", d="2026-09-30", offset=3)
# → "2026-10-10" (a Saturday make-up workday, correctly)
Enter fullscreen mode Exit fullscreen mode

How It Compares

This API Calendarific HolidayAPI.com Abstract API
Countries 501 230+ 250 190+
China adjusted workdays ✅ exclusive
Business days counting
Next business day
US state-level ✅ free paid $399/yr paid
Free tier 1,000/mo 500/mo 1,000/mo 1,000/mo

Pricing

Plan Price Quota
Basic Free 1,000 requests/mo
Pro $10/mo 10,000 requests/mo
Ultra $25/mo 50,000 requests/mo
Mega $100/mo 500,000 requests/mo

No credit card needed for the free tier.


Links

Found a data error? Submit feedback on the API docs page — verified errors are fixed within 48 hours.

Top comments (0)