Would a setMonth() by Any Other Method Name be as Sweet?
On 31st December 2009 a unit test that had been running successfully in the Caplin continuous integration environment for two months suddenly started failing.
The test in question created a JavaScript Date object, modified it using various setter methods, then verified that the format method returned the expected value. The code for this is shown in the snippet below.
var oDate = null;
Test.setUp = function() {
oDate = new Date();
oDate.setYear(2012);
oDate.setMonth(9 - 1);
// September
oDate.setDate(8);
oDate.setHours(17);
oDate.setMinutes(05);
oDate.setSeconds(19);
oDate.setMilliseconds(55);
};
Test.format_Ext = function() {
assertEquals("2012-09-08 17:05:19", oDate.format("Y-m-d H:i:s"));
};
This code may not be to be everyone’s liking, since it seems a verbose way of setting a specific date (it’s mutating an object when it could have constructed it with the correct arguments to start with, and not everyone likes setters). However it seemed surprising that it was suddenly failing. After all this test had been passing for two months, but it was now reliably failing in IE, Firefox and Chrome.
For the record, the failure was caused because the expected value was “2012-09-08 17:05:19″, however the actual value was “2012-10-08 17:05:19″.
It took a little while to get to the bottom of this issue, which was not entirely unsurprising given its cause. When a Date object is constructed with no arguments it represents the date and time at the point it was created. In addition to this, the Date object always wants to represent a valid date. The issue occurs when attempting to set the month to September when the day is the 31st. Since September doesn’t have 31 days this is automatically rolled over to 1st October for us, as illustrated in this commented code snippet below:
Test.setUp = function() {
oDate = new Date();
// year = 2009, month = Dec, day = 31
oDate.setYear(2012);
// year = 2012, month = Dec, day = 31
oDate.setMonth(9 - 1);
// year = 2012, month = Oct, day = 1
// 31st Sep is invalid so it rolls over
// to 1st Oct!
oDate.setDate(8);
// year = 2012, month = Oct, day = 8
// other setters...
};
Looking at the documentation for the Date object it is easy to eliminate this issue by modifying the code to change the year, month and day atomically. Several potential options are shown below:
// my preferred solution - construct the Date object with
// the correct year/month/day to start with
oDate = new Date(2012, 9 - 1, 8);
// or
oDate = new Date(); oDate.setYear(2012, 9 - 1, 8);
// or
oDate = new Date();
oDate.setYear(2012);
oDate.setMonth(9 - 1, 8);
Regardless of which solution you believe is best, the most important lesson that I took away from this is how critical it is to name your methods appropriately. It turns out that the documentation for Date object from both MDC and MSDN provide clear explanations about the behaviour of the method, however they are undermined by the benign nature of its name – most developers have used setter methods ad infinitum and wouldn’t bother to read the documentation.
In our case, the call to setMonth() was made in the good faith that it would set the month to the expected value. It was not anticipated that it would set the day too, which in turn would increment the month to a different value from the one that was set. In fact if the setDate() method had been called the line above setMonth() then this issue would not have occurred, which is contrary to what I expect. I struggle to think of any reason why setters should not be commutative.
This article is not intended to be a criticism of the setMonth() method behaviour, instead it is a reminder to all developers of the importance of method names. Method names should describe what they do since the users of the method may make assumptions about what it will do based on that name without reading any documentation associated with it. Robert C. Martin discusses this in much better detail in the chapter “Meaningful Names” in his excellent book Clean Code: A Handbook of Agile Software Craftsmanship which I would thoroughly recommend to all developers.