LockerGnomeTech news and nostalgia for today's technology professionals
Subscribe

Search

Softwarebillingdatesbugs

A billing system cleared every bill due in January on December 1, because one date check compared month numbers and ignored the year

The Daily WTF's code-horror series ran the reader story under the title Next Month. Nobody was blamed, the dates could be rebuilt, and some customers still lost billing periods.

Chris Pirillo tearing a page off a wall calendar while a pile of envelopes spills into a wastebasket, engraved like a banknote
Illustration: LockerGnome

One of Sean’s colleagues left Initech on good terms. The next day, the billing system that colleague had worked on started failing.

The Daily WTF’s story, “Next Month” by Remy Porter (published November 12, 2026), says “no one truly suspected foul play, it was just a weird coincidence.” Still, a system that dies the morning after its author’s last day makes everyone glance sideways at the calendar. (Nobody says it out loud. Everybody thinks it.)

The date was December 1. Late that evening, “after staring at the code for many hours,” Sean found the line:

if item.next_bill_date.month >= end_date.month
  item.next_bill_date = nil
  item.save
else
  ...
end

The comparison looks at the month number of each date and nothing else. It never asks which year either month belongs to. When it is true, the code sets the next billing date to nil and saves the record, which in a billing system means the customer stops being billed.

The story’s explanation: it “was blowing up in December because every bill’s ending month was greater than or equal to January.” Any month number from 1 to 12 is at least 1, and a bill due in January is month 1 in a year that has not arrived yet. Compared as bare numbers, December’s 12 beats January’s 1, so the code treated a bill still to come as one already past its end.

That is a bug made for a calendar rollover. Through eleven months of a year, a lazy month comparison can pass for a date comparison, since the numbers rise the way the dates do. December is the first month where the next bill belongs to the following year, and the check has no way to know. A test run in March would have sailed through. (A test run on December 1 would have sailed through the wrong door.)

The fix the story’s code calls for is comparing whole dates. The .month attribute gives the month of the year, a number from 1 to 12, and that is all it gives.

Nothing was lost for good, at least not the dates. The billing dates could be rebuilt without a backup, because they follow from each customer’s history. But some customers had lost billing periods, and someone had to go through their histories one at a time.

The departed developer ended up looking better than the code. Nobody had sabotaged anything. The line had probably been running fine since it was written, waiting for the one month that could break it, and the last day of someone’s employment happened to land beside it.

The story does not say who wrote the line or how many customers lost periods, so how big the cleanup got is still an open question.

Expect more bugs like this to be found at year boundaries, and by someone reading code late at night rather than by a test suite. The fix is boring, and the habit that prevents it is boring too: compare full dates, and run the tests on December 31.

Anyone who inherits code from a departed developer inherits its unspoken assumptions about the calendar. The assumption here, that months always rise, held for eleven of twelve.

Sean’s thank-you note called it “an early Christmas present.”

More Software

Every Software story →