A WhatsApp credit runner had the start of the billing day hardcoded as 18:30 UTC. That is exactly midnight in India and exactly wrong everywhere else — and it had been copied to five call sites.
India Standard Time is UTC+5:30. Midnight in Kolkata is 18:30 UTC the previous day. If every
customer you have is Indian, and someone needs the start of the current billing day, then
18:30 is a correct answer to the question being asked. It ships. It works. It
is tested against reality every day by the only market that exists.
Then the platform sells into markets that are not India, and the constant does not follow, because a constant has no idea what it was standing in for.
The credit runner treated 18:30 UTC as the day boundary for every account.
For non-IST markets the runner's day and the merchant's day were offset by the difference
between their local midnight and 18:30 UTC — so the window used to grant, count and reset
WhatsApp credits was not the window the merchant was actually being billed for. In the
worst-affected markets this cost up to 21 of 24 billable hours per day.
The mechanism is not subtle once you draw it. There is a window the system believes is "today", and a window the merchant is contractually being billed for. In India they are the same window. Everywhere else they slide apart by the local UTC offset, and the two shoulders where they fail to overlap are where the accounting goes.
Only the middle band is billed correctly. The two red shoulders are usage attributed to the wrong day — and usage attributed to a day that has already been invoiced is usage nobody ever charges for.
Note what this does not look like from the outside. Nothing errors. No request fails. Credits are granted, usage is counted, invoices are generated, and every number is internally consistent with every other number. The system is confidently wrong, which is the only kind of wrong that survives in a billing system for any length of time.
The constant appearing once is a bug. The constant appearing five times is the thing worth writing about, because it tells you the concept never had a home.
Five places that each need to know when the day starts. Not one of them asked anything; each of them decided.
Duplication like this is not laziness. It is what happens when there is no function to call. The first engineer needed a day boundary and there wasn't one, so they wrote the number. The second engineer needed a day boundary, found the number in the first place, and copied it — which is the correct instinct given the options. By the fifth copy the number looks like a convention, and conventions do not get questioned during code review.
// What five call sites each contained, in one shape or another:
const dayStart = startOfDayUtc().setHours(18, 30);
// What replaced them: one function, one argument, no default.
const dayStart = billingDayStart(account.timezone, at);
The important property of the replacement is not that it handles timezones. It is that it cannot be called without stating whose day you mean. There is no default parameter, because a default would have been IST, and a default that is right for your largest market is how you get here in the first place.
The tests were correct. They asserted that credits reset at midnight and that usage landed in the right day, and they passed, because they ran on machines in IST against fixtures authored in IST by people in IST. The assertion and the bug shared an assumption, so the assertion could never fail on it.
Run the billing suite under at least one timezone that is not your own and not UTC — and pick one with a fractional offset or a DST transition, because those break the arithmetic that a whole-hour offset lets you get away with. A test that only ever runs in one timezone is testing your machine, not your logic.
The other reason it survived: the merchants who were losing hours had no way to see it. What the reporting showed was computed by the fifth call site, using the same constant as the other four. The interface agreed with the runner because it was asking the runner. Nobody ignored a discrepancy — the system was structurally incapable of displaying one.
That is the same failure I wrote about in how to bill AI usage: a value that decides what someone is charged, and no screen anywhere that shows it.
18:30was never a time. It was the sentence "the billing day starts at local midnight, and our customers are in India" — with the second clause dropped, because at the time it went without saying.
Every literal in a codebase is a compressed assumption. Most of them are fine. The dangerous ones share three properties, and this one had all three:
The cheap version of this discipline: grep your codebase for timezone offsets and currency symbols, and for each one ask whose. If the answer is "well, ours", you have found the same bug at a different address.