A few months ago I wrote a blog post about extensions to the Date.prototype
. That post provided ways to add date parts and subtract date parts. Today’s post shows you how to use a generic Date.prototype.diff()
function to find the difference between two dates and optionally return a string specifying the difference. This can be particularly useful if you need to show a countdown until a certain time.
The following is the code that I wrote for a comprehensive date difference function:
// Date.prototype.diff() - By Chris West - MIT Licensed
(function() {
var SEC = 1e3,
MIN = 6e4,
HOUR = 36e5,
DAY = 864e5,
WEEK = 6048e5,
reFormat = /'(''|[^']+)*'|\{([^}\\]+|\\[\s\S])+(?!\\)\}|-?([WY])\3*|-?([SsmhdDM])\4*%?|<[12]:(\w+)(?::(0+))?>/g,
reSlashChars = /\\(.)/g,
reStartComma = /^,/,
reType = /\w%?$/,
reSplit = /,(\\[\s\S]|[^\\,]+)*/g,
reWords = /\W/g;
Date.prototype.diff = function(endDate, format) {
var me = new Date(Math.min(this, endDate));
endDate = new Date(Math.max(this, endDate));
var type,
neg = +me != +this ? -1 : 1,
diff = endDate - me,
types = {
S : diff,
"S%" : diff % SEC,
s : type = parseInt(diff / SEC),
"s%" : type % 60,
m : type = parseInt(diff / MIN),
"m%" : type % 60,
h : type = parseInt(diff / HOUR),
"h%" : type % 24,
d : type = parseInt(diff / DAY),
"d%" : type % 7,
D : type,
"D%" : (type = endDate.getDate() - me.getDate()) < 0 ? endDate.getDate() + parseInt((new Date(me.getFullYear(), me.getMonth() + 1, 1) - me) / DAY) : type,
W : parseInt(diff / WEEK),
M : type = 12 * (endDate.getFullYear() - me.getFullYear()) + endDate.getMonth() - me.getMonth() - (endDate.getDate() < me.getDate()),
"M%" : type % 12,
Y : parseInt(type / 12)
};
if(neg < 0) {
for(var i in types) {
types[i] *= -1;
}
}
types["-"] = neg > 0;
return format
? format.replace(reFormat, function(match, index, $2, $3, $4, datePart, datePartLength) {
if(match.charAt(0) == "'") {
return match.slice(1, -1);
}
if(datePart) {
index = match.charAt(1) == "1" ? me : endDate;
try {
return padNum(index["get" + datePart]() + (datePart == "Month" ? 1 : 0), (datePartLength || "").length);
}
catch(e) {
return match;
}
}
if(match.charAt(0) == "{") {
index = [];
("," + match.slice(1, -1)).replace(reSplit, function(a) {
index.push(a.slice(1));
});
console.log(index);
if(index[2] in types) {
type = types[index[2]];
}
return (index[+!(Math.abs(type) - 1)] || "").replace(reSlashChars, "$1");
}
type = types[match.match(reType)[0]];
index = (type < 0 && match.charAt(0) != "-" ? -1 : 1) * type + "";
match = match.replace(reWords, "");
return padNum(index, match.length);
})
: types;
};
function padNum(num, digits) {
var neg = (num = num + "").indexOf("-") == 0;
return (neg ? "-" : "") + (new Array(Math.max(digits - (num).length + 1 + neg, 1))).join(0) + num.replace("-", "");
}
})();
Let’s say that we have an SPAN
element whose id
attribute is countdown
. If we wanted a countdown until the end of the year to show up in that element, we could write something like the following:
setInterval(function() {
var now = new Date,
nextYear = new Date(now.getFullYear() + 1 + "/01/01"),
format = "M 'month'{s}, D% 'day'{s}, h% 'hour'{s}, m% 'minute'{s}, s% 'second'{s}";
span = document.getElementById("countdown");
span.innerHTML = now.diff(nextYear, format);
}, 1000);
The following JSBin example gives you the ability to experiment with the format string:
JS Bin
The following are more example date differences and format strings that could be used:
alert(new Date("2012/11/08 14:17:00").diff(new Date, "D:hh%:mm%:ss%:SSS%"));
alert(new Date("2012/10/09 14:17:00").diff(new Date, "D 'day'{s}, h% 'hour'{s}, m% 'minute'{s}, s%.SSS% 'seconds'"));
alert(new Date("2012/10/09 14:17:00").diff(new Date, "D 'día'{s}, h% 'hora'{s}, m% 'minuto'{s}, s% 'segundo'{s}"));
String Literals
In order to use string literals in a format string, you must surround your string in single quotes. In order to escape the single, simply prepend it with another single quote.
Date Difference Measurements
There are two different measurements for date differences: full measurements and remainder measurements. All remainder measurements are suffixed by percent signs following the meta characters. The following is a table of all of the date difference meta characters and the corresponding measurements:
Meta Character(s) | Measurement |
---|---|
Y |
The amount of full years between the two dates. |
W |
The amount of full weeks between the two dates. |
M |
The amount of total months between the two dates. |
M% |
The amount of months remaining, not including those counted in the total amount of years between the two dates. |
D |
The amount of total days between the two dates. |
D% |
The amount of days remaining, not including those counted in the total amount of months between the two dates. |
d |
The amount of total days between the two dates. The same as D . |
d% |
The amount of days remaining, not including those counted in the total amount of weeks between the two dates. |
h |
The amount of total hours between the two dates. |
h% |
The amount of hours remaining, not including those counted in the total amount of days between the two dates. |
m |
The amount of total minutes between the two dates. |
m% |
The amount of minutes remaining, not including those counted in the total amount of hours between the two dates. |
s |
The amount of total seconds between the two dates. |
s% |
The amount of seconds remaining, not including those counted in the total amount of minutes between the two dates. |
S |
The amount of total milliseconds between the two dates. |
s% |
The amount of milliseconds remaining, not including those counted in the total amount of seconds between the two dates. |
Padding Measurements with Zeroes
You can pad a measurement with leading zeroes if too short. In order to make sure that a non-remainder measurement has at least two digits, you can simply append the same meta character to the end of itself. The amount of times that a meta character appears next to itself will determine the minimum number of digits to display. The following are examples:
hh
– two digits for the total amount of hoursmm
– two digits for the total amount of minutesss
– two digits for the total amount of secondsSSS
– three digits for the total amount of milliseconds
In order to make sure that remainder measurements are padded, you can use the same principle. Only duplicate the first character of the meta character and prepend it as many times as you want. The amount of times that a meta character appears (not including the percent sign) determines the minimum number of digits to display. The following are examples:
hh%
– two digits for the amount of hours (excluding those counted in the amount of days)mm%
– two digits for the amount of minutes (excluding those counted in the amount of hours)ss%
– two digits for the amount of seconds (excluding those counted in the amount of minutes)SSS%
– three digits for the amount of milliseconds (excluding those counted in the amount of seconds)
Negative Measurements
By default, negative numbers will not be shown. In order to show them, you will need to prefix the meta character string with a negative sign.
Conditional Literal Strings (1-based)
To specify which strings will appear if the meta character was 1 or not 1 you will have to use following format: {DISPLAY_IF_ONE,DISPLAY_IF_NOT_ONE,META_CHARACTER}
.
Section | Optional | Description |
---|---|---|
DISPLAY_IF_ONE |
No | The string to display if the meta character is 1 |
DISPLAY_IF_NOT_ONE |
Yes | The string to display if the meta character is NOT 1. |
META_CHARACTER |
Yes | The singular meta character that will be used as the conditional to determine which of the two preceding strings to display. If not given, this will default to the most recently seen meta character in the entire format string. |
Escaping Special Characters In Conditional Literal Strings
If you want a special character such as a comma or a curly bracket to display, you will need to prefix it with a backslash character.