Jan 012014
 

For a client’s website, I needed to enumerate the 12 months preceding a given date to create links to archived content. The site uses a javascript templating engine to create HTML, offloading the process from the server, so generating the list of months on the client side in javascript seemed like a reasonable choice. For the past week, everything looked great, but suddenly today I noticed that it was repeating the current month.

The code itself is pretty simple. It’s written as a dustjs helper function that uses the Date.setMonth function to handle wrapping from January of one year to December of the previous year.

dust.helpers.month_list = function(chunk, ctx, bodies, params) {
	var count = dust.helpers.tap(params.count, chunk, ctx) | 0;
	var curDate = new Date();

	for (var i = 0; i < count; ++i) {
		var dateStr = (1900 + curDate.getYear()) + '-' + (curDate.getMonth()+1);
		chunk = chunk.render(bodies.block, ctx.push({date : dateStr}));
		curDate.setMonth(curDate.getMonth()-1);
	}

	return chunk;
}

Some quick printf debugging, adding console.log(curDate) at the start of the loop, shows this surprising result:

Tue Dec 31 2013 20:47:14 GMT-0800 (Pacific Standard Time)
Sun Dec 01 2013 20:47:14 GMT-0800 (Pacific Standard Time)
Fri Nov 01 2013 20:47:14 GMT-0700 (Pacific Daylight Time)
Tue Oct 01 2013 20:47:14 GMT-0700 (Pacific Daylight Time)

Apparently, on the 31st of the month, subtracting one month from the current date does not have the desired effect, in Chrome (on Windows 8.1). I ran the test again in IE 11 and observed the same behavior, as well as tried by manually setting the date to the 31st of October and subtracting a month, again seeing the same behavior. I'm not sure if that means this is somehow part of the specification, or if it's a bug caused by a library used underneath the hood in both browsers, but the end result is the same. My "clean" code to use setMonth(getMonth()-1) instead of writing out an if statement to detect the start of the year and wrap correctly now contains a cryptic loop that detects if the month didn't actually change and subtracts it again, all to deal with a bug that only happens once a month.

About Greg Malysa

I am a EE PhD student whose interests include computer architecture, analog circuit design, digital signal processing, and programming in a wide variety of languages. I do a lot of hands-on implementation work, such as doing PCB layout, assembling prototypes, and writing software for both embedded and general purpose systems. I also enjoy research and do many academic or proof-of-concept projects just to see if something can be done. If it involves electricity, I probably think it is interesting.

  3 Responses to “The Once-A-Month Javascript Bug”

  1. The problem is the date (day of the month). By taking Dec 31st and substracting 1 to the month, you’re trying to set a date to November 31st, which doesn’t exist.

    Try in your console creating some dates such as new Date(35,10,2013) and see what you actually get.

    A solution could be setting the date to 1 just after doing curDate = new Date();

    • A friend just brought this up on facebook as well. It is definitely the source of the problem, but it does bring me back to the situation that there’s no clean way to calculate these kinds of deltas using built-in library methods. A reasonable answer to this might be “there’s no general solution because sometimes you want this wrapping behavior and sometimes you don’t.”

      Really, I had wanted to be “clever” and skip the obvious solution of just using a pair of counters, detecting zero and decrementing the other one, but at this point it does seem to be the easiest-to-follow implementation.

    • Rather than: curDate.setMonth(curDate.getMonth()-1);
      Just do : curDate.setDate(0);

      See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate
      “If the dayValue is outside of the range of date values for the month, setDate will update the Date object accordingly. For example, if 0 is provided for dayValue, the date will be set to the last day of the previous month.”

      Also checkout moment.js : http://momentjs.com/

Leave a Reply