Afternoon,
I'm calculating the difference between two dates using JS. I am trying to get a result accurate to a second. I've been getting on very well with it, but noticed an issue. The first two dates shown below show a difference of 5 years, but as soon as I add one more year to the future date, it shows 5 years, 1 Day. Where is the extra day coming from? When the future date hits 2020, it adds another day into the result, this is a recurring pattern. Can someone enlighten me? I don't have any leap year calculations involved yet, I've put my code below:
$(document).ready(function(){
var todaysDate = new Date('2010/11/24 23:00:00');
var futureDate = new Date('2020/11/24 22:59:00');
calculateTime(todaysDate,futureDate);
});
function calculateTime(todaysDate,futureDate){
var difference = futureDate.getTime() - todaysDate.getTime();
var years = Math.floor(difference/1000/60/60/24/365);
difference -= years*1000*60*60*24*365;
var days = Math.floor(difference/1000/60/60/24);
difference -= days*1000*60*60*24;
var hours = Math.floor(difference/1000/60/60);
difference -= hours*1000*60*60;
var minutes = Math.floor(difference/1000/60);
difference -= minutes*60*1000;
var seconds = Math.floor(difference/1000);
var result = years + ' Years, ';
result += days + ' Days, ';
result += hours + ' Hours, ';
result += minutes + ' Minutes, ';
r开发者_运维百科esult += seconds + ' Seconds';
$('#time').html(result)
}
Yep, 2020 is a leap year, so if your initial test was for 2010-2019, then you try 2010-2020, there's an extra day due to the leap year. Even if you're not explicitly handling leap years, the Javascript date object is Leap-year aware, so the difference will include an extra day's worth of seconds.
Try doing a difference between
1) 2010/11/24 -> 2020/02/28
2) 2010/11/24 -> 2020/03/01
You should end up with with 86,400,000 more in the difference.
Yes, the problem is with leap years. Your line
var years = Math.floor(difference/1000/60/60/24/365);
isn't calculating the number of years difference between the two dates, it's calculating the number of 365-day periods between them instead
The biggest error is to not have written a test suite:
- To check for edge case you know at development time
- To easily check later for other edge case you discover
- Testable code is usually more modular (see below the mock object I had to create to workaround the strong link of your function to jQuery: it would have been easier if the jQuery call had been out of
calculateTime
)
This is called test-driven development.
Now here are some others errors I've found:
- Your date strings format does not respect the specification. Your system understand your format, but some may not.
- Your date strings do not specify a timezone, so you will have issues on locale which have daylight saving.
- Your function will give you effective elapsed time, but that may not be what your users are interested in. In particular when daylight saving is involved: you may have +1/-1 hour.
Here is a test suite that works from a Windows command line prompt (use cscript testsuite.js
to run it):
// jQuery mock object for calculateTime testing
// $('#time').html(result) => result is stored in $.result
var $ = (function() {
var $, $$ = {
html: function(result) {
$.result = result
}
}
return $ = function(ignore) {
return $$
}
})();
// Henryz's unmodified function
function calculateTime(todaysDate,futureDate) {
var difference = futureDate.getTime() - todaysDate.getTime();
var years = Math.floor(difference/1000/60/60/24/365);
difference -= years*1000*60*60*24*365;
var days = Math.floor(difference/1000/60/60/24);
difference -= days*1000*60*60*24;
var hours = Math.floor(difference/1000/60/60);
difference -= hours*1000*60*60;
var minutes = Math.floor(difference/1000/60);
difference -= minutes*60*1000;
var seconds = Math.floor(difference/1000);
var result = years + ' Years, ';
result += days + ' Days, ';
result += hours + ' Hours, ';
result += minutes + ' Minutes, ';
result += seconds + ' Seconds';
return $('#time').html(result)
}
function test(a, b, expected) {
var toString = Object.prototype.toString;
if (toString.apply(a) != '[object Date]') a = new Date(a);
if (toString.apply(b) != '[object Date]') b = new Date(b);
calculateTime(a, b);
var got = $.result;
WScript.Echo(
got === expected
? "ok"
: "not ok - ["+a.toString()+"] to ["+b.toString()+"]\r\n# Got: "+got+"\r\n# Expected: "+expected
);
}
test('2010/11/24 23:00:00', '2011/11/24 23:00:00', '1 Years, 0 Days, 0 Hours, 0 Minutes, 0 Seconds');
test('Nov 24, 2010 23:00:00', 'Nov 24, 2011 23:00:00', '1 Years, 0 Days, 0 Hours, 0 Minutes, 0 Seconds');
test('2010/11/24 23:00:00', '2020/11/24 22:59:00', '10 Years, 0 Days, 0 Hours, 0 Minutes, 0 Seconds');
test('2011/03/26 23:00:00', '2011/03/27 23:00:00', '0 Years, 1 Days, 0 Hours, 0 Minutes, 0 Seconds');
test('2010/10/30 23:00:00', '2010/10/31 23:00:00', '0 Years, 1 Days, 0 Hours, 0 Minutes, 0 Seconds');
Here is my output (local timezone (France) offset changed on 2010-10-31 and 2011-03-26):
ok
ok
not ok - [Wed Nov 24 23:00:00 UTC+0100 2010] to [Tue Nov 24 22:59:00 UTC+0100 2020]
# Got: 10 Years, 2 Days, 23 Hours, 59 Minutes, 0 Seconds
# Expected: 10 Years, 0 Days, 0 Hours, 0 Minutes, 0 Seconds
not ok - [Sat Mar 26 23:00:00 UTC+0100 2011] to [Sun Mar 27 23:00:00 UTC+0200 2011]
# Got: 0 Years, 0 Days, 23 Hours, 0 Minutes, 0 Seconds
# Expected: 0 Years, 1 Days, 0 Hours, 0 Minutes, 0 Seconds
not ok - [Sat Oct 30 23:00:00 UTC+0200 2010] to [Sun Oct 31 23:00:00 UTC+0100 2010]
# Got: 0 Years, 1 Days, 1 Hours, 0 Minutes, 0 Seconds
# Expected: 0 Years, 1 Days, 0 Hours, 0 Minutes, 0 Seconds
So you should completely rewrite the function or better, use an existing, tested implementation.
精彩评论