Our schedule wizard had a silent-drop bug with dates typed day first. Type a date the way a lot of the world writes it, save, and the date you typed was not the date that got stored. Anyone whose browser or habits put the day first could hit it.
The first fix was the wrong size
The wizard got its own fix first. It normalized day-first input before the value reached the database, and it worked on the wizard. That was the mistake. I had treated the bug as a property of one screen, so I fixed one screen.
The cost showed up in the ticket queue. A follow-up sat in the tracker saying other admin forms could still drop a typed date without saying so. The wizard was patched, and every other admin form with a date box still had the same hole. Each one had its own way of reading a typed date, so each one failed in its own way.
Sweeping the admin screens for date inputs turned up three different failure modes:
- The field went blank. You typed a date, saved, and it was gone.
- An event moved to today. The reader couldn’t parse the value and fell back to the current date.
- The date saved as 2001. The value was accepted, then landed somewhere it obviously didn’t belong.
All three are silent. No error, no message, nothing to tell staff the date they typed was not the date stored. The wizard was only the first place we noticed it.
One reader instead of seven patches
Seven surfaces had this problem, and I did not write seven patches. The sweep produced a single shared date reader with a fixed contract:
- Take what the person typed.
- Normalize day-first dates into the form the database expects.
- If the input can’t be read, refuse it and say so with a message.
The third rule is the whole point. The old readers each had a fallback, whether blank, today, or a default that turned out to be 2001. A fallback is a decision to write something the user didn’t type. The shared reader has no fallback. It either returns a date it understood or returns an error the form shows to the person who typed it.
Once all seven surfaces called the same function, the bug class stopped being a list of places to remember. A new admin form that needs a date box now has one obvious way to get it, and that way can’t quietly drop the input.
Checking it by hand
I checked it live, not just in tests, by typing day-first dates into each of the seven boxes and confirming the value stuck. I also typed unreadable text into each one and confirmed the refusal message appeared instead of a blank field or a moved event. The change was closed to staging on release 3.362.
The sweep also turned up two unrelated problems. I filed them as their own tickets and kept them out of this change, so the reader change stayed small enough to verify screen by screen. The whole sweep and fix took about an hour and eight minutes.
Search for the other callers before closing
When a bug shows up in one place, I now search for the other callers before I call the first fix done. If the same job is done by seven separate pieces of code, the bug is in the arrangement, and the arrangement is what has to change.