One method that I’ve been wanting for quite a while now was a simple way to format old JavaScript dates in a “pretty” way. For example “2008-01-28T20:24:17Z” becomes “2 hours ago”. Here’s some more examples:
prettyDate("2008-01-28T20:24:17Z") // => "2 hours ago" prettyDate("2008-01-27T22:24:17Z") // => "Yesterday" prettyDate("2008-01-26T22:24:17Z") // => "2 days ago" prettyDate("2008-01-14T22:24:17Z") // => "2 weeks ago" prettyDate("2007-12-15T22:24:17Z") // => undefined
Note that I only care about dates in the past (by far the most common use case) and only dates within the past month (anything beyond a month becomes fuzzy and impractical).
JavaScript Pretty Date
- pretty.js (Also include a .prettyDate() jQuery plugin, for convenience.)
- Demo (Some examples of date conversion using basic DOM manipulation.)
Example Usage
In the following examples I make all the anchors on the site, that have a title with a date in it, have a pretty date as their inner text. Additionally, I continue to update the links every 5 seconds after the page has loaded.
With plain DOM:
function prettyLinks(){
var links = document.getElementsByTagName(“a”);
for ( var i = 0; i < links.length; i++ )
if ( links[i].title ) {
var date = prettyDate(links[i].title);
if ( date )
links[i].innerHTML = date;
}
}
prettyLinks();
setInterval(prettyLinks, 5000);[/js]
With jQuery:
[js]$("a").prettyDate();
setInterval(function(){ $("a").prettyDate(); }, 5000);[/js]
About
There’s a variety of these functions around, in various languages, but I wanted one in JavaScript for a couple reasons:
Microformats – Microformats specify a scheme for passing ISO dates to the client (precise date/time value) while providing the user with a “casual” date representation. I wanted to be able to generate a pretty version of the date, from a Microformat, easily and painlessly.
Auto-update – Secondly, I wanted to have these Microformat dates update automatically, as the page remained loaded. This way, if a user left a page open for 15 minutes, and came back, the pretty dates would be silently updated to their current times. This may seem nitpick-y but Twitter fails to do this and it drives me absolutely insane.
Write once, use everywhere – If I wanted to have these dates be constantly updated I would have these options: Only generate these dates on the server – re-querying new results via an Ajax request, generate the results on the server and on the client, or generate the results exclusively on the client. Client makes the most sense as it would result in the least amount of code duplication and general overhead.
I hope this will be useful to some, I know it’s helped me out a bunch.
By the way – let’s say this piece of code was from a – theoretical – larger project that was attempting to create an easy-to-use, Open Source, Twitter clone – would anyone be interested in using it?
abhik (January 29, 2008 at 2:28 am)
For the sake of completeness, you might want to support dates older than a month. This python function (http://pylonshq.com/docs/webhelpers/rails/date.py.html) is similar and it shouldn’t be too difficult to convert to javascript..
pi (January 29, 2008 at 2:34 am)
ever heard of the datejs library (datejs.com)? i am in no way affiliated with that library, but i guess the NIH syndrome is still strong nowadays :)
sil (January 29, 2008 at 2:38 am)
I might be interested in the twitter clone thing…I keep feeling the pull of Twitter and resisting, and it not being proprietary would help :)
Yoan (January 29, 2008 at 3:22 am)
I don’t think it will cost you that much to deal with other timezones than UTC.
Dr Nic (January 29, 2008 at 4:06 am)
@pi – I think datejs converts in the other direction, taking “yesterday” and parsing it to yesterday’s date object.
Sebastian Redl (January 29, 2008 at 6:17 am)
Can you add localization hooks to this? Ideally, the various strings should be confined to a single, small locations.
Dimitri Glazkov (January 29, 2008 at 8:29 am)
Also here http://antarctica.uab.edu (including microformats support) since late 2006.
Mike Purvis (January 29, 2008 at 9:28 am)
A variation of this idea is when labeling items that have been posted at date X and edited and then edited at some later date Y. What’s best in that case is to first associate the edit with the present “posted 2 hours ago, edited 5 minutes ago,” and then once it recedes far enough into the past, associate it instead with the original post time, eg “posted 2 days ago, edited 2 hours later.”
Anyhow, that’s sort of beyond the scope of this library, but it’s a cute concept once you’re dealing with more than a single date.
ewhitten (January 29, 2008 at 9:29 am)
I would second the twitter clone. As an added bonus, maybe it could have a reliable API and an uptime of more than, say… 20 mins?
Trying to write code against them is painful at times. if I see that stupid blue bird one more time…
timothy (January 29, 2008 at 10:40 am)
I’m mostly interested in the coding. I would have used if and switch statements. I think the booleans are clever.
Hamish M (January 29, 2008 at 11:17 am)
Great work, John. Now if only I’d had this a few months ago.
And what’s this talk of an OS twitter clone? I’m interested.
timothy (January 29, 2008 at 11:39 am)
(I just saved 22 characters in my code rewriting a nasty chain of if statements as a boolean assignment chain.)
Jackson (January 29, 2008 at 11:49 am)
Very nice!
I think I’d modify it slightly, to either not refresh every 5 seconds (e.g. 10-15 seconds instead), or enhance it to start the refreshing, closer to the next change.
e.g. if already at the “4 days ago” level, there’s no point in refreshing until seconds before the next 24 hour period rolls around.
Not that I would expect someone to keep the page loaded around the clock, but for each date that is in this zone, it would save 28,000 threads firing off un-necesary updates.
Adam (January 29, 2008 at 12:23 pm)
I can think of a few things that are missing from this script. How about “2 days from today” or “real soon now”?
I’d also like to see the reverse (perhaps in a different script). If I’m going to look for flights, how cool would it be to say “a month from now” or something.
Jeff L (January 29, 2008 at 1:46 pm)
Would the theoretical Twitter clone support OpenID? Not sure about interest though, I’m only just starting to get into Twitter and finding a few folks I know on there.
If your Twitter clone could post updates both to itself AND Twitter, I’d probably have much more interest.
Brad Fults (January 29, 2008 at 3:26 pm)
It would be neat if this script calculated the deltas between each represented time and its respective granularity, to save on updates.
For instance, if all of your times are “X hours ago” or more, it’s wasteful to re-render it every 5 seconds. This is the case with your demo page right now.
If you had several items that were “X minutes ago”, a 60-second interval would be sufficient for the re-render and so on.
Marc Grabanski (January 29, 2008 at 5:57 pm)
Call it, “jQuery-itter”. I bet that would really draw in a solid user base. ;)
As for an OS Twitter, I would be interested in it more from the perspective of contributing / watching the code base develop. I’m not personally very interested in Twitter, but seeing the source code develop of a social phenomenon clone developed by Mr. Resig – that would be interesting.
Jörn Zaefferer (January 30, 2008 at 10:28 am)
David Pollak and the other lift folk are working on an Twitter-like experiment (I think they call it Scritter). That could be a good base to add some nifty UI tricks.
Matt (January 30, 2008 at 11:37 am)
Nifty.
Although 1 week*s* looks a little odd, heres a quick patch for that last line (I think I am doing it correctly).
day_diff < 31 && Math.ceil( day_diff / 7 ) + " week"+ ((day_diff>7)?'s':'') +" ago";
David Lynch (January 30, 2008 at 12:35 pm)
@Marc: “jitter”. :D
Pete (February 1, 2008 at 12:49 pm)
It seems the script assumes that input date is in local timezone rather than utc (as the format says it is).
Parsed input date used to calc diff from “now” is:
2008-02-01T17:07:00Z => Fri Feb 01 2008 17:07:00 GMT+0100 (CET)
Which is wrong.
If I’ve understood the current function correctly, the given date and local date must be from the same timezone (not input as utc).
I would like to see diff = now_utc – input_utc.
Ergin Tekinbas (February 1, 2008 at 1:56 pm)
Nice work, John! Clean an simple output. Thanks for sharing.
henrah (February 9, 2008 at 7:36 am)
Here is an equivalent that uses a loop instead of the huge default-operator cascade:
function timeDifference(dateStr){
var time = ('' + dateStr).replace(/-/g,"/").replace(/[TZ]/g," ");
var seconds = (new Date - new Date(time)) / 1000;
var i = 0, format;
while (format = timeDifference.formats[i++]) if (seconds < format[0])
return format[2] ? Math.floor(seconds / format[2]) + ' ' + format[1] + ' ago' : format[1];
return time;
};
timeDifference.formats = [
[60, 'seconds', 1],
[120, '1 minute ago'],
[3600, 'minutes', 60],
[7200, '1 hour ago'],
[86400, 'hours', 3600],
[172800, 'Yesterday'],
[604800, 'days', 86400],
[1209600, '1 week ago'],
[2678400, 'weeks', 604800]
];
Dean Landolt (February 9, 2008 at 8:11 pm)
@Henrah
To me that’s much more understandable code. So I went ahead and pimped out the list a little (pasted below) to handle everything through 20 centuries (I suppose I could go millenia or eons, but that just seems silly. I also made some changes to allow for “from now” strings as well. I’m also looking into setting up for UTC so that should round out the trifecta of complaints I saw in the comments. I’ll post a link to it once I finish it up.
@John:
An open source twitter clone sounds pretty interesting. With twitter’s infrastructure issues, it’d be great to see some of that functionality where we can have some control over it.
function pretty_date(date_str){
var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," ");
var seconds = (new Date - new Date(time)) / 1000;
var token = 'ago', list_choice = 1;
if (seconds < 0) {
seconds = Math.abs(seconds);
token = 'from now';
list_choice = 2;
}
var i = 0, format;
while (format = time_formats[i++]) if (seconds < format[0]) {
if (typeof format[2] == 'string')
return format[list_choice];
else
return Math.floor(seconds / format[2]) + ' ' + format[1] + ' ' + token;
}
return time;
};
var time_formats = [
[60, 'just now', 1], // 60
[120, '1 minute ago', '1 minute from now'], // 60*2
[3600, 'minutes', 60], // 60*60, 60
[7200, '1 hour ago', '1 hour from now'], // 60*60*2
[86400, 'hours', 3600], // 60*60*24, 60*60
[172800, 'yesterday', 'tomorrow'], // 60*60*24*2
[604800, 'days', 86400], // 60*60*24*7, 60*60*24
[1209600, 'last week', 'next week'], // 60*60*24*7*4*2
[2419200, 'weeks', 604800], // 60*60*24*7*4, 60*60*24*7
[4838400, 'last month', 'next month'], // 60*60*24*7*4*2
[29030400, 'months', 2419200], // 60*60*24*7*4*12, 60*60*24*7*4
[58060800, 'last year', 'next year'], // 60*60*24*7*4*12*2
[2903040000, 'years', 29030400], // 60*60*24*7*4*12*100, 60*60*24*7*4*12
[5806080000, 'last century', 'next century'], // 60*60*24*7*4*12*100*2
[58060800000, 'centuries', 2903040000] // 60*60*24*7*4*12*100*20, 60*60*24*7*4*12*100
];
Dean Landolt (February 10, 2008 at 1:14 am)
So I worked out the UTC issue — all you have to do is add Date.getTimezoneOffset() * 60000) to the seconds diff line. I think that’s all there is to it. I’ve written it up here: http://deanlandolt.com/archives/163
Kevin Olbrich (February 12, 2008 at 10:28 pm)
This is also a handy technique for page caching. If you cache it with the JS date/time, then you don’t need to dynamically render the page to make the dates look right, they get updated by the browser.
Ken Gregg (February 14, 2008 at 3:57 pm)
Re: Jitter. I have been thinking a lot about a distributed twitter-like microblogging system, mostly about the messaging layer between sites. Would be very interested in hearing more.
As Jeff L said, I think an interfacing to Twitter, if only for im and sms support, is important.
Lei Huang (February 20, 2008 at 2:20 am)
I found a problem with this JS
when the user and Server are not in the same Time Zone… it got a problem…..
Restamon (February 23, 2008 at 1:44 pm)
Thanks, I was just looking for that: http://blog.restafarian.org/?p=41. My dates are just a tad different, though (“2008-01-28T20:24:17-08:00” vs. “2008-01-28T20:24:17Z”), so I may have to make a slight modification, but other than that, this looks like just what I was after. Thanks, again … very nice!
Chris Pitt (February 26, 2008 at 5:05 am)
I overhauled the code and added 2 other (related) plugins, and included localization support for the system I’m building:
1. $.fn.format_date (formerly prettyDate)
$('a').attr('title', '2008-02-24 10:00:00').addClass('format_date');
//$('.format_date').format_date();
//Date == '2008-02-24 12:00:00'
>> 2 hours ago
2. $.fn.format_duration
$('a').attr('title', '2008-02-24 10:00:00').addClass('format_duration');
//$('.format_duration').format_duration();
//Date == '2008-02-24 10:15:30'
>> 15 minutes 30 seconds
3. $.fn.format_time
$('a').attr('title', '67').addClass('format_time');
//$('.format_time').format_time();
//Current date not used
>> 1 minutes 7 seconds
I’m sure it can be much improved and refined, but I learned a lot from coding it, and would be happy to share it who wants it.
cgpitt [at] gmail [dot] com
fjyghjkghjk (February 29, 2008 at 8:47 am)
gjkkkjgkjhkgjkkkkkkk
fjyghjkghjk (February 29, 2008 at 8:48 am)
hdfg dghhhh dffhgd
sdp (March 4, 2008 at 2:35 am)
jgbjvbdvbdvnropivhr
vruvrvreovijopfeuwifwehlf
m cscjknc fknc jfbjbf
londynek (March 15, 2008 at 7:13 am)
Please don’t spam ? what are you doing this ?
londynek (March 15, 2008 at 7:13 am)
Admin should delete all stupid replay like spam.
ét (March 21, 2008 at 9:33 pm)
tetét
Zach Leatherman (March 23, 2008 at 8:43 pm)
Dean, your code is incorrect. For one, you assume that “Tomorrow” is anything greater than 24 hours and less than 48 hours from now.
If it’s 8PM on Sunday, and the comparison Date is 7AM on Tuesday, you would return “Tomorrow.”
Zach Leatherman (March 23, 2008 at 9:55 pm)
Hey Dean, I found a few more errors. I documented them and put an updated script here:
http://www.zachleat.com/web/2008/03/23/yet-another-pretty-date-javascript/
Maxine Hendricks (March 24, 2008 at 4:50 am)
bracketing hypobranchiate hyaloid upstate vitiate stigmatization peptogaster bobber
New Line, Inc.
http://www.oaklandmekanix.com/
londynek (March 28, 2008 at 7:39 pm)
VEry good articles, big thats for YOu. It’s really good and my opinion it;s not spam :)
comp (April 6, 2008 at 7:51 pm)
very good thx for you brother
xabi (April 9, 2008 at 9:40 am)
I’m using this one:
function pretty_date(date_str) {
var iGeneratedTime = new Date(('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," ")).getTime() / 1000;
var iNow = Math.round(new Date().getTime() / 1000);
var iDiff = (iGeneratedTime 0 ? iDays + " day" + (iDays == 1 ? "" : "s") + " ": "");
sText += (iHours > 0 ? iHours + " hour" + (iHours == 1 ? "" : "s") + " ": "");
sText += (iMinutes > 0 ? iMinutes + " minute" + (iMinutes == 1 ? "" : "s") + " ": "");
sText += (iSeconds > 0 ? iSeconds + " second" + (iSeconds == 1 ? "" : "s") + " ": "");
sText += "ago.";
}
return sText;
}
xabi
xabi (April 9, 2008 at 9:42 am)
Sorry, I didn’t conter the > and < characters :)
function pretty_date(date_str) {
var iGeneratedTime = new Date(('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," ")).getTime() / 1000;
var iNow = Math.round(new Date().getTime() / 1000);
var iDiff = (iGeneratedTime < iNow ? iNow - iGeneratedTime : -1);
var sText = "";
if (iDiff < 0) {
sText = "Not sure!"
} else {
var iSeconds = iDiff % 60;
iDiff = (iDiff - iSeconds) / 60;
var iMinutes = iDiff % 60;
iDiff = (iDiff - iMinutes) / 60;
var iHours = iDiff % 24;
iDiff = (iDiff - iHours) / 24;
var iDays = iDiff;
sText += (iDays > 0 ? iDays + " day" + (iDays == 1 ? "" : "s") + " ": "");
sText += (iHours > 0 ? iHours + " hour" + (iHours == 1 ? "" : "s") + " ": "");
sText += (iMinutes > 0 ? iMinutes + " minute" + (iMinutes == 1 ? "" : "s") + " ": "");
sText += (iSeconds > 0 ? iSeconds + " second" + (iSeconds == 1 ? "" : "s") + " ": "");
sText += "ago.";
}
return sText;
}
RzSlwLGdbFbvPNvwvI (May 18, 2008 at 5:27 am)
RHCUgf
Mukunda Modell (May 19, 2008 at 5:58 am)
Holy shit, a “theoretical” open twitter clone? Of course people would be interested in using it!!! Can has twitter clone pls? kthx!
joseph (May 20, 2008 at 5:13 pm)
good site dude mature tease upskirt :))
James Rose (May 22, 2008 at 5:37 pm)
Same sorta thing in PHP: http://programmr.net/entry/distance_of_time_in_words_PHP
liza (May 23, 2008 at 7:35 pm)
i say one thing buy louis replica vuitton 788932
sylvia (May 30, 2008 at 5:49 pm)
good work man nfl playoff tickets
8[[[ wimbledon tickets 145
kate (June 3, 2008 at 5:38 pm)
it’s nice site!!! lipitor addiction
ylrpp
lola (June 10, 2008 at 6:21 pm)
please look at this free nextel ringtone converter >:[
Alex Weber (June 11, 2008 at 5:56 am)
i just translated this to portuguese… silly really formats are different here so the number needs to be wrapped by the text in some situations instead of being followed by it always…
return day_diff == 0 && (
diff
Maybe you can think of a more convenient way to introduce multilingual support but here is the portuguse snippet of code if anyone needs it! :)
Alex Weber (June 11, 2008 at 5:57 am)
for some reason that got broken…
diff
Alex Weber (June 11, 2008 at 5:57 am)
ok i give up, its on my website… thx