The following post appears to be older than 100 days. I therefore cannot guarantee that any technical information in this post is still valid today.

Please consider to also look for other, more up to date resources!
2008/07/11

(Smart) Dates In Python

Leslie Polzer has a small series of blog posts running where he presents his readers short problems, solved in Common Lisp and asks his readers to present solutions in other languages. ”Smart Dates In CL”, the latest post, is about presenting dates, with different offsets from the current date, in human naming conventions. For example 46sec ago, 10min ago, Yesterday.

I made a version using Python. It uses the datetime module and some simple math (which is the only type of math I am capable of ;-)). Because I am 100% self taught, I am somehow not really convinced that my solution is the most pythonic way to solve this.

Here's where you enter the game. I know that some of you know Python very well, so, what did I wrong and what could be done better in the following short piece of code? Is there some sort of algorithm that can do what I did in a smarter way? Or is there even a Python module available to handle this?

smartdate.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
from datetime import datetime, timedelta
 
def smartdate(now, offset):
    delta = {}
 
    delta['d'] = offset / (60*60*24)
    if delta['d'] > 0:
        offset = (offset - ((60*60*24) * delta['d']))
 
    delta['h'] = offset / (60*60)
    if delta['h'] > 0:
        offset = (offset - ((60*60) * delta['h']))
 
    delta['m'] = offset / 60
    if delta['m'] > 0:
        offset = (offset - (60 * delta['m']))
 
    delta['s'] = offset
 
    if delta['d'] > 1:
        if delta['d'] > 6:
            date = now + timedelta(days=-delta['d'], hours=-delta['h'], minutes=-delta['m'])
            return date.strftime('%A, %Y %B %m, %H:%I')
        else:
            wday = now + timedelta(days=-delta['d'])
            return wday.strftime('%A')
    if delta['d'] == 1:
        return "Yesterday"
    if delta['h'] > 0:
        return "%dh%dm ago" % (delta['h'], delta['m'])
    if delta['m'] > 0:
        return "%dm%ds ago" % (delta['m'], delta['s'])
    else:
        return "%ds ago" % delta['s']
 
if __name__ == '__main__':
    now = datetime.utcnow()
    offsets = [ 36, 90, 120, 130, 3599, 3600, 3601, 86400, 86500, 173000, 14290010 ]
    for offset in offsets:
        print "%d: %s" % (offset, smartdate(now, offset))

Here's a sample output of the above snippet:

% ./smartdate.py
36: 36s ago
90: 1m30s ago
120: 2m0s ago
130: 2m10s ago
3599: 59m59s ago
3600: 1h0m ago
3601: 1h0m ago
86400: Yesterday
86500: Yesterday
173000: Wednesday
14290010: Monday, 2008 January 01, 00:12

Do you have a better solution, or can you point out some things I did wrong? Let me know in the comments :-).

You can leave code snippets in the comments by using the following syntax!

<code python>
Your code goes here!
</code>

Comments

1

Not wanting to rain on your parade and not knowing Python, I think the actual output isn't as good as it could be.

“1h0m ago” is not a human naming convention - and I know that is the result presented in the Leslie Polzer's blog entry. If “Yesterday” is correct, then “An hour ago” should be the result for 3599, 3600 and 3601.

Similarly, 90 is “One and half minutes ago”, 120 is “Two minutes ago”.

2008/07/11 13:04
2

Well the keys to your delta dictionary could be more verbose and I'd probably make “constants” (I know that Python does not have constants ;)) of things like 60*60 just for readability.

Apart from that it's probably how I'd do it.

2008/07/11 13:15
3

Hmmmm, that's a good point and I have to agree. Though, that would of course require quite some more logic.

2008/07/11 13:45
4

Agreed on the dictionary keys, that's a typical matter of laziness ;-), I could have been more verbose there. Hmmm, about the “constants” thingy, I personally find 60*60*24 quite readable. I wonder how you'd go about naming them?

2008/07/11 13:50
5

Probably something like “TO_MINUTES” for “60*60” which is not a good idea at second glance cause it's against semantics.

The better idea would probably be to inherit from timedelta and create your own class that supplies the proper data to you. An advantage would be that you wouldn't have to create a timedelta instance out of the data from the delta dictionary later because it would already be a timedelta, just with added functionality.

The problem of “human” printing is one that you can sink megabytes of code in with disgusting trees of “if bigger than this and smaller than this” that won't do you much good. I guess the most reasonable thing to do would be to eliminate 0 values so that “1h0m3s” becomes “1h 3s”.

2008/07/11 14:06
6

The humanized tag library for django does some similar things

http://www.djangoproject.com/documentation/add_ons/#humanize

2008/07/11 15:00
7

This doesn't meet your requirements but is something I had already written recently, to do the same kind of thing:

def friendly_date(d):
	now = datetime.now()
 
	if d.year <= (now.year - 1):
		return "Years ago"
 
	if d.year == (now.year - 1):
		return "Last year"
 
	if d.month <= (now.month - 1):
		return "Earlier this year"
 
	if d.month == (now.month - 1):
		return "Last month"
 
	if d.day <= (now.day - 6):
		return "Earlier this month"
 
	if d.day == (now.day - 1):
		return "Yesterday @ %02d:%02d" % (d.hour, d.minute)
 
	if not d.day == now.day:
		return weekdays[d.day]
 
	if d.day == now.day:
		return "Today @ %02d:%02d" % (d.hour, d.minute)
2008/07/12 09:10
8

I would probably eliminate the dictionary, it's not buying you anything here, and do the math like:

def smartdate(now, offset):
    delta_s = offset % 60
    offset /= 60
 
    delta_m = offset % 60
    offset /= 60
 
    delta_h = offset % 24
    offset /= 24
 
    delta_d = offset
 
    # ... output code uses the above variables, same otherwise
Jamie Briggs
2008/07/22 21:44
9

Did you try to inherit from timedelta? Seem`s like this is impossible.

Damir
2009/05/08 11:54



JTFHA