It’s easy to think that the relative-time style of “this comment was written 15 minutes ago” is incompatible with caching. How are you supposed to cache something if the text changes every minute? Static pages with JavaScripts, that’s how!
I put together a new mini app for our new status site yesterday that needed exactly this technique. I wanted the content of the application to be entirely page cached, so it would withstand the onslaught if the terrible should happen and we need to redirect all trafic to the status site.
I also wanted the relative time style, especially since it’s timezone independent and you don’t want people to think you’ve been down for two hours when it’s really only been twenty minutes because they did the timezone conversion wrong.
It looks like this:
To make this trick work, I embed the time stamp for a given entry in the DOM as a custom attribute that I can query for conversion like this:
<li>
<span time="Feb 05, 2009 15:30:00 GMT">09:30 AM CST (15:30 GMT)</span> /
The aliens came out of no where and took Basecamp!
We're trying to hunt them down now. Stay tuned while we bring the lasers online.
</li>
I only want to convert the entries from today into relative time. The entries from yesterday and before should just use the specific time-only style. This means looking for just the span’s inside li’s from the ul with the class “today”. They’re then converted when the document is loaded with this function:
convert_all_times_from_today_to_words: function() {
$$('.today li span').each(function(e) {
e.innerHTML = this.time_ago_in_words_with_parsing(e.getAttribute('time'));
}, this);
}
The function uses a simple DateHelper JavaScript class that mimicks the DateHelper in Rails.
This technique can be used for things other than just dates. You could imagine a cached page where you wanted the name “David Heinemeier Hansson” replaced with the text “You” if there was a match.
It’s a great way around otherwise cache-busting requirements.
Sean
on 05 Feb 09Awesome idea! Coincidentally, I’m in need of such a solution right now. Thanks for sharing.
Josh Kim
on 05 Feb 09A very cool idea indeed. Although, this won’t pass validation, right? Or am I missing something… But really, I can’t think of any other way to get this functionality. Anyone else?
Justin Reese
on 05 Feb 09Very smart technique for a situation like this! Thanks.
Can I offer two suggestions on your particular implementation? I know it wasn’t your point to solicit suggestions, but oh well. :)
1. Replace ” / ” with ”: ” as the time/status separator for increased readability. (“8 minutes ago / This happened” isn’t quite as syntactically correct as “8 minutes ago: This happened”.)
2. Rather than use a custom DOM attribute, you could use a hidden input field in the (which targeted the with previous() or next()). I know, pragmatism > standards, but still. ;)
I was going to suggest the “X hours uptime” message be cumulative, but I’m assuming it’s currently a one-line “do we have a status update for today? If no, put 24 hour” bit, and there’s no reason to complicate that.
Cheers,
Ema
on 05 Feb 09Great, thanks! maybe it’s a newbie question, but how did you manage the static pages?
I put together a new mini app
is it a Rails app with static pages? any hint on doing that?
thanks!
Justin Reese
on 05 Feb 09Darn, I should have known my HTML tags would be stripped. There was an LI and a SPAN up there in the #2 comment. You can probably figure out where.
Alex
on 05 Feb 09I like simple solutions.
Trek Glowacki
on 05 Feb 09I’ve been using this trick for a while now with a similar javascript helper (although mine is a prototype class where a function clearly will do).
One of the nice aspects of this technique is that the time is automatically displayed in the current user’s timezone (based on their system settings).
Ignacio
on 05 Feb 09Josh, I guess that to keep validation you can slightly modify the code and add a classname to the span to use and use it as timestamp holder, something like class=”ts_121222772727”, then instead of parsing the time attribute you parse the classname. BTW, clever approach!
Gary Haran
on 05 Feb 09You can make this validate by using this example:
http://gist.github.com/58792
Essentially you remove the time attribute and add it if it doesn’t exist from the value inside the span itself.
mikkel
on 05 Feb 09I acutally rote about this back in 2006!
http://markmail.org/message/mlbwzmum3ema34ee
read the post, there is some other nice pointers in the thread…
DHH
on 05 Feb 09Yeah, this won’t validate, but as we may have hinted at earlier, I don’t really care. HTML is designed to let browsers ignore attributes they don’t understand. That’s good enough for me.
Ema, this is a small Rails 2.3 app. 78 lines of code ;).
Mislav
on 05 Feb 09Nice tip! I’ve updated the code to take more advantage of Prototype and to update every 2 minutes. This has been written quickly, but the general idea is there.
Trek Glowacki
on 05 Feb 09There was some talk of ousting inline javascript from Rails helpers. Can we expect more this kind of code in Merb2/Rails3? Rails writes the document, and a javascript “driver” (as DHH called them) adds functionality?
Jim Jeffers
on 05 Feb 09Excellent post. I don’t think enough devs take the time to realize how much they can do on the client. You could easily make this validate if you wanted to by simplifying. Just use the span’s inner html instead of the time attribute. No need to provide it in your markup twice.
DHH
on 05 Feb 09Trek, yes. Rails 3 is scheduled to move forward with the custom attribute approach to JavaScript into of inlining.
Jim, I want two different things. The relative time can’t be generated from the specific timestamp used in the example for Thursday.
I didn’t want to generate two different things from my Rails app depending on the day, but yeah, in this example you could make it validate if you cared.
Trakk
on 05 Feb 09Ok.. So, what happens if the user’s local machine’s time is not correct?
Is there a way to grab the date/time from the HTTP response headers and ignore the local time?
Sudrien
on 05 Feb 09The client having an incorrectly set system clock can mess with the values displayed.
Thankfully this issue doesn’t come up frequently due to automatic time synchronization being enabled on most systems.
Jim Jeffers
on 05 Feb 09@DHH yes that makes sense. I don’t care that much about validation on custom attributes. It’s more so using elements that don’t belong in certain other elements that cause front end head aches. A lot of people are advocating making the jump to HTML5 early. If you do that you can make whatever attributes you want: http://ejohn.org/blog/html-5-data-attributes/
They claim other browsers that don’t support the new doctype will still render most of your work properly. I may try it next time around. Fascinating stuff to keep track of either way.
James Bebbington
on 05 Feb 09If you use jQuery I can highly recommend the timeago plugin. It makes JS-powered relative timestamps a piece of preverbal.
Barry Welch
on 05 Feb 09Wonderful! You’ve all but removed the only obstacle that was preventing me from caching my pages. Thanks.
andy
on 05 Feb 09@josh – If you are that worried about getting the html to pass validation you could use a hidden div.
@DHH – Love the simple solution, but wouldn’t it be even easier to just print the time right on the screen? Couldn’t the users just ahhhh…. look at a clock? If that was the solution, then you wouldn’t need any javascript at all!
Ryan McGeary
on 05 Feb 09@James Thanks for extra plug on my jquery.timeago plugin. It was written for exactly this scenario. Almost a perfect microformat use case.
Michael
on 05 Feb 09We use the same technique on Eureka Science News for about a year, works great, degrade perfectly.
kimblim
on 05 Feb 09That’s just a stupid, stupid attitude to have. Fine that your stuff doesn’t validate by design, but to say that you don’t care? Why not say that you prioritise something else instead of “I don’t care”?
Oh, and why not use the “title”-attribute on the span? It will validate and it won’t break anything.
RS
on 05 Feb 09Because it’s the truth :)
zero0x
on 05 Feb 09That’s brilliant idea!
And yes, is dumb, it won’t validate .. But there are many alternatives, so it’s really greate idea!
Jim Garvin
on 05 Feb 09Nice post, David.
+1 for the jquery.timeago plugin. Very elegant.
ceejayoz
on 05 Feb 09@kimblim There’s no reason to care about a validation failure that will, by design, not cause any issues for browsers. Validation is a helpful tool, but it is not a requirement.
Brenton
on 05 Feb 09Those of you arguing standards vs pragmatism are all missing a big one:
Change the attribute to ‘data-time.’ It’s standards-compliant and practical at the same time.
DHH
on 05 Feb 09Brenton, it’s standard compliant with HTML5, which isn’t finished yet. So it won’t please people interested in doing validation against HTML4. But I actually agree, I’ll start doing the data- style going forward to be HTML5 compatible and just continue not to care about the validation errors it’ll bring for HTML4.
efalcao
on 05 Feb 09@DHH I love the method above, and think that you totally have the freedom to do a time= attribute on your sites, but definitely consider thinking twice about going into rails (vs a class=”rails_remote_form”) because some framework users (my employer and our institutional customers) have a business requirement to pass validation and accessibility tests.
Love the direction though. Big time. Keep up the good work.
David Vrensk
on 05 Feb 09On using standards: The Microformats crowd tend to use abbr elements for their dates and datetimes, with the reasoning that most date formats are abbreviations for the full date-time. So your span would be
<abbr title=”2009-02-05T15:30:00UTC”>09:30 AM CST (15:30 GMT)</abbr>
Standards compliant, practical and clever, methinks.
Greg
on 05 Feb 09I’ll give another +1 to jquery.timeago, which is quite slick and easy to use (and standards-compliant, even!)
Henrik N
on 05 Feb 09David Vrensk: I believe I’ve read criticism of that on the grounds that screen readers will read the computer-format title and not the human-format date.
David HH: If I understand you correctly, you only relativize the times, with the date headers being hard-coded to your time zone. That could get a little confusing around midnight, getting e.g. a late Wednesday time listed under Thursday.
I’m glad you show the user’s time zone explicitly, by the way. I’ve seen JS implementations of showing client-local time where the time zone was not shown explicitly and thus making the information near useless (who assumes client-local time on the web?).
I personally like a combination of zoned absolute time and relative time, e.g. “3 minutes ago at 12:34 CET”.
Henrik N
on 05 Feb 09Oh, and a related thing: with relative times, it’s a good idea to have your JS keep updating the relative times at an interval. E.g. Twitter will tell you that the tweet you just posted was “less than 5 seconds go”, even when you revisit that tab an hour later to post again.
troll
on 05 Feb 09ITS NOT STANDARDS COMPLIANT!
ITS NOT STANDARDS COMPLIANT!
ITS NOT STANDARDS COMPLIANT!
ITS NOT STANDARDS COMPLIANT!
:-) I also don’t care. It works.
Jack
on 05 Feb 09Nice! Recently came across this in a Drupal site I’ve done; now must hit it with jQuery..
Dan Glegg
on 05 Feb 09Nice technique, but why put the date in twice? There’s no need to sling a custom attribute on the node there, whether you care about standards or not (I do care).
Stick the date value to be parsed in the body of the span (which is human-readable so the degraded version works) and replace it directly. The custom attribute is not needed.
Jordan Dobson
on 05 Feb 09Why you wouldn’t just use title attribute on the span?
Thanks for sharing, clever implementation.
David Cramer
on 05 Feb 09I am VERY glad to see you using a custom attribute on this. the `rel` and `title` fields are way overused, and this is exactly where the XML design comes into play with HTML.
In response to the poster above, the reason that there is an attribute storing the time here, and he’s not using the value of the tag, is because you may store it as “15 minutes ago” or something similar. How do you calculate when 15 minutes ago was if it’s cached?
john2
on 06 Feb 09While we’re talking about javascript: when you define a function, put a space before the parantheses and format it nicely. it’s much easier to read…
var do_something = function (x, y) {
alert(x + y);
};
NOT
doSomething=function(x,y){
//alert(x+y)
}
J
on 06 Feb 09Loving these type of articles.
Clint Ecker
on 06 Feb 09We do this on arstechnica.com except we render it differently. In static form it looks like this:
2009-02-06T03:45:25Z
We use the jQuery timeago plugin to convert them to fuzzy representations like this on DOM ready:
about an hour ago
Clint Ecker
on 06 Feb 09We do this on arstechnica.com except we render it differently. In static form it looks like this:
<abbr class=”timeago datetime” title=”2009-02-06T03:45:25Z”>2009-02-06T03:45:25Z</abbr>
We use the jQuery timeago plugin to convert them to fuzzy representations like this on DOM ready:
<abbr class=”timeago datetime” title=”2009-02-06T03:45:25Z”>about an hour ago</abbr>
Jose
on 06 Feb 09Scroll down to “Times Zones”
http://hoth.entp.com/2009/2/5/february-2009-tools-on-the-edge
Amit Mani Mishra
on 06 Feb 09It’s really a great idea..
i’ll try to implement it.
Regards,
Amit Mani Mishra
DHH
on 06 Feb 09john2, I guess that’s a matter of preference. I hate dangling parenthesis’ myself, but I agree that there should be spacing around an equal sign and use indention. I used all that in the coding examples.
Julian
on 06 Feb 09I think that’s a great technique. You can cache the page and the “time ago” is still always right.
But it you think about it, it’s not a very big problem that would bring the site down. If you display “x minutes ago”. How often would you need to update that? Every minute. That means every minute a fresh page to the cache. Makes 60 pages per hour. For thousands of peoples coming to the site, fetching 60 pages per hour should be no problem. No real “cache busting” here.
Marc
on 06 Feb 09We’re using this on a product we’re about to launch, and were going to blog about it in the near future.
We’re also firing the function to formet the times periodically so that if a user leaves a tab open with relative times on, they’re still up to date when they return to that tab.
This discussion is closed.