I recently required a pretty-date function to format some twitter dates; I knew there were already quite a few of these floating around the net; but I wanted to give it a try myself:
Regular pretty-date:
Note: Remember, pretty-dates are not meant to be accurate; they’re meant to be “pretty” – once you go past months it does get pretty inaccurate; afterall, logically, when we’re talking about time difference, what is a month? 30 days? Four weeks? The amount of days in a year divided by the amount of months in a year? The same dilemma occurs with larger measures like years; – if I say “one year ago”, are you thinking 365 days? yes? – well then 5 years ago would have to be [365*5] days, right? Nope, not if you assume one of those years is a leap year!
var niceTime = (function() { var ints = { second: 1, minute: 60, hour: 3600, day: 86400, week: 604800, month: 2592000, year: 31536000 }; return function(time) { time = +new Date(time); var gap = ((+new Date()) - time) / 1000, amount, measure; for (var i in ints) { if (gap > ints[i]) { measure = i; } } amount = gap / ints[measure]; amount = gap > ints.day ? (Math.round(amount * 100) / 100) : Math.round(amount); amount += ' ' + measure + (amount > 1 ? 's' : '') + ' ago'; return amount; }; })(); |
Usage:
niceTime( 1 ); // => "39.57 years ago" niceTime( "Sun Mar 01 20:20:02 +0000 2009" ); // => "4.65 months ago" niceTime( "July 19, 2009 12:06:00" ); // => "26 seconds ago" |
I then decided to take it a step further and create a recursive version that keeps going until it hits the smallest measure of time available:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | var prettyTimeDiff = (function() { var ints = { second: 1, minute: 60, hour: 3600, day: 86400, week: 604800, month: 2592000, year: 31536000, decade: 315360000 }; return function(aTime, bTime) { aTime = +new Date(aTime); bTime = bTime === undefined ? +new Date() : +new Date(bTime); var timeGap = Math.abs(bTime - aTime) / 1000, amount, measure, remainder, smallest; for (var i in ints) { if (timeGap > ints[i] && ints[i] > (ints[measure] || 0)) { measure = i; } if (!smallest || ints[i] < smallest) { smallest = ints[i]; } } amount = Math.floor(timeGap / ints[measure]); if (timeGap > 31536000) { /* Handle leap years */ timeGap -= Math.floor(ints[measure] * amount / 31536000 / 4) * 86400; } amount += ' ' + measure + (amount > 1 ? 's' : ''); remainder = timeGap % ints[measure]; if (remainder >= smallest) { amount += ', ' + arguments.callee( +new Date() - remainder*1000 ); } return amount; }; })(); |
I decided to call it prettyTimeDiff
as it can actually take two dates and then will give you the difference between them in pretty-format. If you only pass one date then it’ll assume you want to compare it to today.
Usage:
prettyTimeDiff( 0 ); // => "3 decades, 9 years, 6 months, 2 weeks, 4 days, 51 minutes, 25 seconds" prettyTimeDiff( "July 1, 2005", "July 2, 2006" ); // => "1 year, 24 hours" prettyTimeDiff( 0, 5000 ); // => "5 seconds" prettyTimeDiff( "Sun Mar 01 20:20:02 +0000 2009" ) + ' ago'; // => "4 months, 2 weeks, 4 days, 37 minutes, 49 seconds ago" |
You can limit what units it uses to measure time; for example, if you don’t want decades
then just delete ints['decade']
. The function will use whatever it can; so, if you only want it to measure time in minutes, years and decades then simply comment-out/remove all the other units:
var ints = { //second: 1, minute: 60, //hour: 3600, //day: 86400, //week: 604800, //month: 2628000, year: 31536000, decade: 315360000 }; |
An example with the above adjustment:
prettyTimeDiff( "January 19, 1988" ) // => "2 decades, 1 year, 262873 minutes" |
It’s quite quick so could easily be used within any number of applications; for example, if you ever needed to countdown to the year 3000!
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
Well done %)
I really like the way you write JS π
wow.. your code shine! it tooks me a while to understand all the intricacies..
there’s just one thing I don’t get: why the plus before new?
such in
time = +new Date
?You did an amazing job with this! I can see all kinds of uses, plus the recursive aspect makes it all the more efficient. Great job!
Like the others said, I just like looking at the code you write. It’s so good looking.
Thanks for the comments! π
@Valentino, That is the unary plus operator (
+
) and is a quick way of casting any value to its numerical form (normally viavalueOf()
). E.g.Read more here: http://xkr.us/articles/javascript/unary-add/
@Valentino
+value will convert value into a Number. For a date, that will give you the number of seconds since January 1st 1970, or the result of calling date.getTime().
I don’t use JavaScript often, and I haven’t tested this, but why not loop over decreasing intervals:
If the map doesn’t go in order like that, maybe use something that does.
Brilliant timing [sic]: I was just looking for a way to showcase a count down to my baby’s birthday on his site ββnever too early to manage fans ; )
Also my excuse to leave a comment and say how much I’ve been enjoying/learning from your site, James.
Cheers,
@josh, Unfortunately, the order of JavaScript hashes cannot be relied upon; you’ll get different results across different implementations. Another technique I considered was to have two arrays; one containing the time units (“second”, “minute” etc.) and the other containing their respective values (1,60,3600 etc.)…
nice! I didn’t even know existed a “unary plus operator”! π
Hi James, I wonder, does the arguments.callee( +new Date() – remainder*1000 ); is the recursive part in your code?
I tried to google for arguments.callee, but some references I found tells that this property has been deprecated.
Do you plan to implement another recursive way to replace it?
Thanks
@Ricky,
I also saw that this was something deprecated. I don’t know why; I don’t think there is any other way to do this without knowing the name of the function, which, when anonymous, is not possible. I would think it is not something that will ever actually be removed from language support.
@James
We can use the date objects’ get/set methods to avoid having to define year and month in seconds. That way, we get pretty and accurate!
Note: couldn’t figure out how to prevent munging of lesser-than and greater -than characters.
Doh, return statement could be simplified to this, which would also eliminate some unnecessary recursion:
Still with messed-up special characters, sorry. π
Nice work David! I always forget about the native Date methods… Some of them are actually quite useful. I can’t quite get your solution to work though; it seems to work with past dates but, for example, I can’t get it to work with:
@Ricky, yup, it’s deprecated; I have no idea why though, there really is no better alternative unless you want to use a named function within the closure and then return it by its identifier. I agree with Ben though; I doubt it’ll be released from implementations because so much code on the web depends on it.
@James: Doh, sorry! I made the stupid mistake in the code of assuming that aTime is less than bTime. Inserting this line after assignment of aTime and bTime solves the problem:
Hi James,
Can u explain the pros and cons of writing your code like you wrote and like this: