Converting a list of dates into a shorter, combined list
Join the DZone community and get the full member experience.
Join For FreeForgive the title, I'm not sure it best describes the task. I was asked by a reader to consider a simple problem. Given a list of dates, how would you rewrite them so that two (or more) consecutive dates are displayed together? For example, imagine this input.
dates = [(May 1, 2013), (May 4, 2013), (May 5, 2013), (May 7, 2013)]
I want to take this list and join the values that are one day apart. I should end up with:
dates = [(May 1, 2013), (May 4, 2013 - May 5, 2013), (May 7, 2013)]
I wrote two solutions for this - one in ColdFusion and one in JavaScript. Let's start with ColdFusion.
First I create my sample data and a new array that will store my results.
dates = [ createDate(2013, 6, 10), createDate(2013, 6, 11), createDate(2013, 6, 14), createDate(2013, 6, 16), createDate(2013, 6, 17), createDate(2013, 6, 18), createDate(2013, 6, 22), createDate(2013, 6, 25), createDate(2013, 6, 29) ]; writeDump(var=dates, label="Initial data"); //new array for our formatted data newDates = [];
Now for the real "meat" of the logic. My idea here was to store objects in the new date array. The object contains a first and last property referring to the first and last date. What this allows for is a quick date comparison. If the next item in my source data is one day after the last value in the previous range, than we 'extend' the range by resetting the last property. Otherwise we need to add a new item in the result array.
for(x=1; x<=arrayLen(dates); x++) { if(x == 1) { arrayAppend(newDates, {first:dates[1],last:dates[1]}); continue; } //is our date 1d past last date of previous? if(dateDiff("d", newDates[arrayLen(newDates)].last, dates[x]) == 1) { writeoutput('date diff is one'); newDates[arrayLen(newDates)].last = dates[x]; } else { arrayAppend(newDates, {first:dates[x], last:dates[x]}); } }
Finally, let's make this easier to use by doing some formatting on the array elements. We will loop through each item and add a 'formatted' key.
arrayEach(newDates, function(idx) { if(idx.first == idx.last) { idx.formatted = dateFormat(idx.first, "long"); } else { idx.formatted = dateFormat(idx.first, "long") & " - " & dateFormat(idx.last, "long"); } });
And the result is this:
Woot. Ok, now let's look at the JavaScript version. I'll just share the complete template first and talk about the differences.
function oneDayApart(d1, d2) { var d1m = d1.getTime(); var d2m = d2.getTime(); var oneday = 1000 * 60 * 60 * 24; return (d2m - d1m) <= oneday; } function dtFormat(d) { return d.toDateString(); } var dates = [ new Date(2013, 6, 10), new Date(2013, 6, 11), new Date(2013, 6, 14), new Date(2013, 6, 16), new Date(2013, 6, 17), new Date(2013, 6, 18), new Date(2013, 6, 22), new Date(2013, 6, 25), new Date(2013, 6, 29) ]; console.dir(dates); var newDates = []; for(var x=0, len=dates.length; x<len; x++) { if(x == 0) { newDates.push({first:dates[0],last:dates[0]}); continue; } //is our date 1d past last date of previous? if(oneDayApart(newDates[newDates.length-1].last, dates[x])) { newDates[newDates.length-1].last = dates[x]; } else { newDates.push({first:dates[x], last:dates[x]}); } } //At this point we are done... console.dir(newDates); //But lets make it prettier newDates.forEach(function(elm, idx, array) { if(elm.first != elm.last) { elm.formatted = dtFormat(elm.first) + " - " + dtFormat(elm.last); } else { elm.formatted = dtFormat(elm.first); } }); console.dir(newDates);
Ignore our two helper functions on top for now. Our initial seed data very similar to the ColdFusion one. Instead of using dateNew we use the Date constructor.
The first loop is also pretty similar. Do remember that JavaScript arrays begin with 0. The main issue we have is doing the date comparison. There is no dateDiff in JavaScript. You can find great Date libraries out there, but as I had a very simple need here, I just wrote a quick function that compares the millisecond values of two dates and sees if the difference is less than one day. (By the way, the best JavaScript data library out there - imo - isMoment.js.)
Finally, I did my formatting. Again, I could have grabbed a library for this, but instead I simply ran toDateString. My dtFormat function is a bit simple. Almost too simple to even be it's own function. But I was imaging that I'd probably want to make formatting a bit more complex in the future. This lets me handle that later.
The result:
Note - I switched to Firefox for the screenshot as I think it prints objects nicer in the console.
I've included a zip with the complete code for both examples. Note the CFM has a tag-version as well for older CF engines.
Published at DZone with permission of Raymond Camden, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments