July 10, 2008
Smart dates in CL
Sometimes it’s convenient to present dates in a way that depends on their offset from the current time.
For example, in different resolutions: 23 seconds ago, one minute ago, two days ago.
Another example, making use of human naming conventions: yesterday, Monday (implicitly assuming the closest Monday before the current date).
In Common Lisp, without further babbling:
(load "time.lisp") ;; http://cybertiggyr.com/gene/pdl/time.lisp ;; you could also use, for example, CL-L10N. (defmacro base-bind (unit-var amount (&rest var-and-radix) &body code) "Thanks to Alan Crowe for this wonderful macro." (if (endp var-and-radix) `(let ((,unit-var ,amount)) ,@code) (let ((transfer (gensym))) `(multiple-value-bind (,transfer ,unit-var) (floor ,amount ,(cadar var-and-radix)) (base-bind ,(caar var-and-radix) ,transfer ,(cdr var-and-radix) ,@code))))) (defun smart-date (then) (let ((now (get-universal-time))) (base-bind now-sec now ((now-min 60) (now-hour 60) (now-day 24)) (base-bind then-sec then ((then-min 60) (then-hour 60) (then-day 24)) (base-bind diff-sec (- now then) ((diff-min 60) (diff-hour 60) (diff-day 24)) (cond ;; add more stuff here (e.g. negative offsets) and modify to suit your needs ((> diff-day 6) (CYBERTIGGYR-TIME:format-time nil CYBERTIGGYR-TIME:*FORMAT-TIME-FULL* then)) ((> diff-day 1) (CYBERTIGGYR-TIME:format-time nil "%A" then)) ((= diff-day 1) "Yesterday") ((> diff-hour 0) (format nil "~Dh~Dm ago" diff-hour diff-min)) ((> diff-min 0) (format nil "~Dm~Ds ago" diff-min diff-sec)) (t (format nil "~D seconds ago" diff-sec)))))))) ; demonstration/test (loop for offset in (list 36 90 120 130 3599 3600 3601 86400 86500 173000 14290010) do (format t "~D: ~A~%" offset (smart-date (- (get-universal-time) offset)))) ; output: 36: 36 seconds ago 90: 1m30s ago 120: 2m0s ago 130: 2m10s ago 3599: 59m59s ago 3600: 1h0m ago 3601: 1h0m ago 86400: Yesterday 86500: Yesterday 173000: Tuesday 14290010: Sunday, 2008 January 27, 03:06 +1
Again, I’d like to see solutions from other languages.
Comments(11)
Ok, to save you from having to indent the above comment again I’m trying bbcode (a preview function would really help ;-)).
Here’s my attempt on it in python (keep in mind that I haven’t done much with python yet, so this could probably be done in a more pythonic way):
Thanks for the Python version! :)
Use
<pre lang="LANG">to mark code blocks, where LANG is a valid GeSHi language identifier.I’ll probably install a comment preview plugin.
Don’t worry, editing comments isn’t such a huge chore. I don’t have to reindent stuff.
Aha thanks for the info :-).
I really like posts like this one, solving these little problems is always fun, so keep ‘em coming!
P.S.: Btw, it seems all “>” in my comment were converted to “>” (and I just noticed that the variable “base” in the function is obsolete, a leftover from previous attempts).
Here is my python version. A few pieces such as the date format strings were borrowed from Michael Klier’s version.
I tried to duplicate your CL algorithm because I thought it was clever.
Yeah, you know I prefer CL, but… lovely! :)
Well, a Java solution must be presented, also; Joda-time: http://joda-time.sourceforge.net/
Looks good! Too bad it’s in Java…
floor’ing thrice? um, what about subtracting first then flooring?
btw, greetz in your blog is not something i would expect to see. i would like to see the documentation of that macro. it’s hard to figure out what your syntactic sugars do.
http://www.google.com/search?q=alan+crowe+base-bind
Unfortunately Alan hasn’t provided documentation for BASE-BIND, either.
But it’s not very hard to figure out from the name and the usage in the code.
You’re right of course that it doesn’t end up numerically optimal. How about posting a version with that flaw fixed? :)
Why do you nest the base-bind 3 times? Seems like you only use the diff-* variables in the body.
PS: wordpress uses horizontal space really well (NOT!)