I add 24 hours when I want tomorrow. Everybody knows that this breaks twice a year, everybody says the word timezone in the review, then the change goes in with the 24 hours still in it because the fix is a library nobody wants to add today. That is roughly how it went for me in March. What I had never done is look at what the database says the year actually contains.
So I read all of it. Every zone the runtime knows about, the offset on each day of 2011, with every change refined down to the minute by bisecting inside the day. That is 418 zones, of which 185 moved at least once, for a total of 344 jumps.
336 of those 344 are the hour everybody argues about. The other 8 are the interesting ones.
Lord Howe Island moves by half an hour, which is the only zone in the database that does. The Antarctic stations move by 2 hours, twice for Troll and once for Davis. Casey moves by 3. Those are 6 jumps that a test suite full of 1 hour cases will never see.
Then there is 30 December, which has not happened yet. It is 9 days away.
The day that will not exist
At 10:00 in the morning UTC on that day, Apia goes from minus 600 minutes to plus 840. An hour later Fakaofo does the same. Both are stepping across the date line, so the local calendar goes from Thursday 29 December straight to Saturday 31 December. For anybody standing there, Friday will not occur.
Every schedule that counts days by adding 86400000 milliseconds will produce a Friday in those zones. Every report that groups by local date will have an empty bucket in the middle. Every difference between 2 timestamps computed as days will be 1 too big.
I do not have a way to test that today. Neither does anybody else. The rule sits in the database with a date on it. That is the entire warning anybody is going to get.
Spring forward, fall back, 26 exceptions
The count that made me suspect my own script: 186 of the jumps go forward and only 158 go back. Time is supposed to come home by the end of the year, so a difference of 28 looked like a bug in the way I scan the year boundary.
It is not a bug. 30 zones finish 2011 on a different offset than they started it.
25 of the 30 are Russia and Belarus, which moved to summer time in March and stayed there. Minsk is on that list, which is the part I did not know until the script printed it. I have been reading that wall clock all year. The 26th zone at plus 60 is Bahia in Brazil, which is simply in the middle of its summer when the year ends.
What the code has to survive
186 forward jumps mean 186 stretches of local time in 2011 that do not exist. Ask for 02:30 on the wrong night in the wrong zone and there is no such instant. 158 backward jumps mean 158 stretches that happen twice, so a local timestamp without an offset is ambiguous by an hour. On Lord Howe Island it is ambiguous by half an hour.
And the offsets themselves are not the round numbers a schema tends to assume. Across the year the database uses 41 distinct offsets from UTC. 15 of them are not whole hours: the familiar half hours plus 5.75, 8.75, 12.75 and 13.75.
What I did not check
Whether the database I am reading today tells the truth about January. Zone data gets corrected after the fact, so the file describing 2011 is a file that has been edited during 2011. My scan cannot tell a rule from a later correction of a rule. I also read only what the runtime ships, which is 1 copy of the database rather than the several that a system usually has, so the operating system, the language and the database engine may each answer from a different vintage. That mismatch is a bug I have hit before and did not measure here.
The narrow claim is a rule of thumb about what to assume. A day is not 24 hours, an offset is not a whole number of hours, the clock does not come back by the end of the year. And in 9 days a Friday goes missing for 2 zones that have every right to expect Friday.