Tuesday, December 19, 2017

On a Y2k18 Bug - School boy error or sadistic API?

Being and older developer, I remember all the fuss about the Y2K bug.  For the younger readers, shortly before the millennium, there was huge panic that mankind would suffer enormous catastrophe as we moved from the year 1999 to 2000.  The cause of this concern was the short-sighted way some computer programs had been storing dates.  We had been using a couple of numbers to store the year - like 77 for 1977 and 85 for 1985.  Developers of that early software had made the assumption that their programs would not be running by 1999 and therefore thinking about numbers greater than 99 (1999) was not required.
Back in those days, storage space and memory were scarce and expensive resources, so developers often had to be a little creative about how they stored data.

As the millennium approached, the concern was that many of those programs were in fact still running, powering the back office of businesses, stock markets, factory machinery, flight deck control systems and so on.  The scaremongerers suggested that as midnight struck on the final day of 1999,  planes would fall from the skies, trading systems would go into melt down and factories would grind to a halt with their equivalent of the blue screen of death.

A disproportionate number of Y2K consultancies sprang up, charging  exorbitant fees to help remove or mitigate the risk of Y2K within all kinds of organisations.  What actually happened?  Well, not a lot. Planes did not fall from the sky, nuclear reactors did not go critical and everyone either breathed a huge sigh of relief or muttered 'meh'.

Whether catastrophe had been averted by the hard work and diligence of those consultancies, driving well managed upgrade plans and mitigation strategies, or it had simply not been that much of an issue is hard to say, though I'm pretty sure thousands of column inches have been penned on the subject.

Anyway, a bug was recently discovered in an application I'm working on. The bug is embarrassing, trivial and could have been problematic for some of our customers, had we not found, identified and fixed the issue in quick succession.

So, here's a quick programming trivia quiz. What is the date represented by the following line of Java 7 code?

 Date specialDay = new Date(2017, 12, 25);

All the Java devs out there who have been bitten by this (and that is probably all of them), will be laughing now, stating the quite obvious answer of 25th January 3918.

Developers fluent in other languages are completely entitled to have a WTF? moment at this point.  It's pretty widely accepted that the APIs offered by the  Java Date / Time related classes were not the most friendly, sensible or predictable.  In fact, many of them are downright obtuse and offer the developer a hundred ways to hang himself before breakfast.  For completeness, I must say that these crimes against readable code have been remedied in more recent versions of Java 8 with the eventual adoption of much of the great work done by the JodaTime library.

So what was going on in that example?

Well, it's one of those oddities that the bulk of Java developers will know.  The Date constructor Date(2017, 12, 25) takes 3 parameters:
1. A 1900 based year offset
2. A ZERO based month number
3. A day number

There is scope to use the GregorianCalendar class here instead:

Date specialDay = new GregorianCalendar(2017, 12, 25).getTime();

which will yield a much closer result of January 25th 2018.  The year is now an absolute value as Pope Gregory XIII's 1582 calendar update introduced the BC and AD concepts that we have all been getting used to.  The British (with some alleged resistance) finally adopted the GregorianCalendar in 1752, which included forgetting the days between September 2nd and September 14th.  Too many years using the preceding Julian Calendar - with it's greater inaccuracy - had left us several days adrift of where we should have been from a solar orbit point of view, so this was remedied at the same time.  Those who had a holiday to Disney World booked for Sep 3rd were deeply upset.

Whilst quirky and interesting, the above is a history lesson.  It is not the sort of trivia that the majority of developers carry to their work place on a daily basis, eager to apply their keen knowledge of history to their Android app development. And while you have to admire the dogged determination of the original Java designers to build a class library that not only obeyed the 'rules' of object orientation but also supported multiple calendar systems out of the box, I can't help thinking that they would have been better off building something with a 'happy path' that fit the needs of the 99%...

To the non-historian, the Date API simply looks like crazy inconsistency, and that zero based month index number in the middle is a real kicker.  The seasoned Java pro would *probably* be on top of things, but it only takes a momentary lapse of concentration from a poor nights sleep to unconsciously enter the 'obvious' value - which would be wrong. It's also much more likely these days that a developer may be frequently switching between 3 or 4 different languages as part of their normal development process.

So technically, with a zero based month index number, December is month 11. Specifying month 12 of 2017 really means the 13th month of 1900+2017 which of course wraps round to become the 1st month of 3918 hence resulting in Jan 25 3918.

In defence of Java, the documentation in this area is clear and definitve:

"In all methods of class Date that accept or return year, month, date, hours, minutes, and seconds values, the following representations are used:
 - A year y is represented by the integer y - 1900.
 - A month is represented by an integer from 0 to 11; 0 is January, 1 is February, and so forth; thus 11 is December.
 - A date (day of month) is represented by an integer from 1 to 31 in the usual manner.
"

One might argue that developers are well used to zero based indexing, with arrays, characters within a string, screen co-ordinates and the like. However, these are all programming concepts, whereas we spend our whole lives appreciating that December is the 12th month of the year, and unless you are really concentrating, that is the overriding cognitive process when one is dealing with dates.

Having fallen foul of this issue, most Java devs would have made sure to use month constants from Calendar, but it's not uncommon to stumble across code where this practice hasn't been applied.

 Date specialDay = new Date(2017, Calendar.DECEMBER, 25);

So yeah, the old Java Date APIs have no doubt lead to many an issue within the typical application, and when creating great code is hard enough, it feels like some sort of sadistic punishement when an API has been 'designed' and implemented with such brutal gotchas. Most modern APIs are more carefully designed around the 'Pit of Success' ( https://blogs.msdn.microsoft.com/brada/2003/10/02/the-pit-of-success ) principles - and rightly so.

The cognitive load of a developer should be focused on solving her business problem or implementing that tricky algorithm, NOT trying to remember the nasty trivia of an ill thought out core class.  Having fallen foul of the Java date classes so many times over the years, I still dare not go near such code without a bucket of unit tests - which is no bad thing.

In the world of great apps and great websites, an enormous amount of effort goes in to creating 'products' which behave in the way that users expect them to behave. A class library is a product to a developer, and should be designed to behave as the average developer would expect, NOT to chastise them for a poor memory of trivia or history.


No comments: