Somewhere in most codebases there is a line that looks like this:
is_working_day = day.weekday() < 5It reads as obviously true. Monday to Friday, work. Saturday and Sunday, rest. I have written that line myself, more than once, and I meant it every time.
Then I spent a month building HolidayOps, an API whose whole job is answering "is this a working day?" for 250 countries, and that line turned out to be wrong in five separate ways. None of them are exotic. All of them have cost someone a late payment, a missed delivery or a meeting nobody could attend.
Here are the five, and what I do now instead.
1. Everyone rests on Saturday and Sunday
Most of the world does. Not all of it.
When I counted across every country the holidays library supports, 222 rest on Saturday and Sunday. Eighteen rest on Friday and Saturday, including Saudi Arabia, Egypt and Qatar. Eight rest on Sunday only. Iran rests on Friday alone. Brunei takes Friday and Sunday off and works the Saturday in between.
That weekday() < 5 line marks a Sunday in Riyadh as a day off, when it's the first day of the working week, and a Friday as a working day, when the office is closed.
What I do instead: the weekend is data, looked up per country. Never a constant in the code, never a default that a country can forget to override.
2. A country's weekend never changes
This one surprised me more than the first.
The United Arab Emirates moved its weekend from Friday-Saturday to Saturday-Sunday at the start of 2022. Other countries have made similar moves over the years. If your system stores one weekend per country, it gets every date before the change wrong, and the work that cares most about working days, payroll, leave balances, contract deadlines and settlements, is exactly the work that looks back at old dates.
What I do instead: ask for the weekend per date, not per country. In HolidayOps the domain layer asks the library whether a specific date falls on that country's weekend, and the library knows when the rule changed. The answer for 3 January 2021 and 3 January 2022 can differ, and should.
3. Holidays are known in advance
Some are. Many aren't, not exactly.
Holidays that follow the lunar calendar move by about eleven days every year, and their exact date can depend on the sighting of the moon. Governments also add days at short notice: a national celebration, a period of mourning, an extra day joined to a long weekend.
You can't solve that with a better algorithm. You can only be honest about it. HolidayOps takes its data from the open-source holidays library, which is updated several times a year by people who follow these announcements far more closely than I can. The terms of service say plainly that for dates with legal or financial weight, you should check the official source as well.
What I do instead: treat holiday data as something that changes. Pin the library to an exact version, upgrade it on purpose, and cache holiday lists under a key that includes that version, so an upgrade can never serve stale dates.
4. A working day is either on or off
Real calendars have more states than that.
Some countries move a working day onto the weekend to bridge a holiday, so a Saturday becomes a working day and the Friday before it becomes a day off. Companies run half days before a festival. They close for a week the government never declared. A team can choose to work a Saturday at the end of a quarter.
If the only state your code knows is a boolean, every one of those becomes a special case somebody has to remember.
What I do instead: every answer carries a reason, not just a yes or a no. HolidayOps answers with one of a short list: a normal working day, the weekend, a public holiday, a weekend day moved to a working day, a company closure, a half day or an extra working day. Half days count as working days in the maths, because that's how payroll treats them, but they are flagged so a scheduler can still avoid them. The reason is what lets a person trust the answer, because they can see why.
5. Everyone agrees what today is
They don't, and this one bit me in my own product.
At 2 a.m. in Dubai it is still yesterday in New York. "Is today a working day?" has no single answer for a team that spans both. My first landing page computed today in UTC, which meant that for the first five hours of every day, it showed yesterday to the people it was mostly built for.
Then the cache made it worse. The page was cached for six hours, so just after midnight it could keep showing the old "today" until the cache expired. Nothing failed loudly; it was simply, quietly wrong.
What I do instead: "today" is always someone's today. Each country is read in its own time zone. Where a country spans several, I use the capital's, because the standard data lists zones alphabetically and would otherwise start the United States in Adak, Alaska. Anything cached that mentions today carries the hour in its key, because somewhere a new day starts every hour.
The habit underneath all five
Every one of these bugs came from the same place: date logic written in more than one spot.
A helper in the API, a slightly different one in the email job, a third in the frontend. Each looked correct on its own. They just didn't agree with each other, and when two parts of a product disagree about a date, the person reading both stops trusting either.
So in HolidayOps there is exactly one place that knows what a working day is. It's a small package of plain Python with no web framework imports, it has full branch coverage, and everything else calls it: the API, the calendar feeds, the webhooks, the monthly email and the public pages. When I fix a date bug, I fix it once, and every surface gets the fix at the same time.
If you take one thing from this, take that. Not a library, not a pattern. Just a rule: one place decides what day it is.
The rest of the build, the architecture, the security and the mistakes, is in the HolidayOps case study.
Up next
Keep reading.
01RAG Retrieval Strategies on a 50k-Document Dataset: BM25 vs Dense vs Hybrid
How BM25, dense embeddings and hybrid search (RRF) behave on a 50k-document RAG corpus: working code, honest trade-offs, and when each one actually wins.
11 minThe Engineering Logs
02The Discount That Knew You Were Hesitating: Rule-Based Personalisation in an Online Pharmacy
How Zovita+ turns browsing into personal discounts without a machine-learning model: one visitor key for guests and customers, interaction counters, six offer rules, one pricing function for the bag and the charge, offers locked and redeemed once at checkout, guest-to-account merging, A/B tests, and the edge cases that shaped it.
10 minThe Engineering Logs
03One Seat, Two Buyers: Race Conditions, IDOR and Keys in a Cinema Booking System
How BookMyMovie makes sure one cinema seat is never sold twice, a guessed booking number shows nothing, and a leaked or missing key can't quietly break tickets: row locks, lock order, a NULL-tolerant unique key, status compare-and-set, idempotency, IDOR scoping, HMAC tickets, captcha step-up, and the bugs that still got through.
11 minThe Engineering Logs
