Displaying Useful Relative Dates in Python
When creating interfaces, a lot of times an iso formatted date isn't what your users are really looking for. They want something to jog their memory so they can go "Oh, that's the file I uploaded yesterday" rather than "That's the file I uploaded October 3rd... hmm, was that before or after I went on vacation..."
Anyway, using Python's wonderful datetime library (and timedelta), here's some code to do really nice relative dates for you. (Also as a gist here: https://gist.github.com/3503910)
def reltime(date, compare_to=None, at='@'):
r'''Takes a datetime and returns a relative representation of the
time.
:param date: The date to render relatively
:param compare_to: what to compare the date to. Defaults to datetime.now()
:param at: date/time separator. defaults to "@". "at" is also reasonable.
>>> from datetime import datetime, timedelta
>>> today = datetime(2050, 9, 2, 15, 00)
>>> earlier = datetime(2050, 9, 2, 12)
>>> reltime(earlier, today)
'today @ 12pm'
>>> yesterday = today - timedelta(1)
>>> reltime(yesterday, compare_to=today)
'yesterday @ 3pm'
>>> reltime(datetime(2050, 9, 1, 15, 32), today)
'yesterday @ 3:32pm'
>>> reltime(datetime(2050, 8, 31, 16), today)
'Wednesday @ 4pm (2 days ago)'
>>> reltime(datetime(2050, 8, 26, 14), today)
'last Friday @ 2pm (7 days ago)'
>>> reltime(datetime(2049, 9, 2, 12, 00), today)
'September 2nd, 2049 @ 12pm (last year)'
>>> today = datetime(2012, 8, 29, 13, 52)
>>> last_mon = datetime(2012, 8, 20, 15, 40, 55)
>>> reltime(last_mon, today)
'last Monday @ 3:40pm (9 days ago)'
'''
def ordinal(n):
r'''Returns a string ordinal representation of a number
Taken from: http://stackoverflow.com/a/739301/180718
'''
if 10 <= n % 100 < 20:
return str(n) + 'th'
else:
return str(n) + {1 : 'st', 2 : 'nd', 3 : 'rd'}.get(n % 10, "th")
compare_to = compare_to or datetime.now()
if date > compare_to:
return NotImplementedError('reltime only handles dates in the past')
#get timediff values
diff = compare_to - date
if diff.seconds < 60 * 60 * 8: #less than a business day?
days_ago = diff.days
else:
days_ago = diff.days + 1
months_ago = compare_to.month - date.month
years_ago = compare_to.year - date.year
weeks_ago = int(math.ceil(days_ago / 7.0))
#get a non-zero padded 12-hour hour
hr = date.strftime('%I')
if hr.startswith('0'):
hr = hr[1:]
wd = compare_to.weekday()
#calculate the time string
if date.minute == 0:
time = '{0}{1}'.format(hr, date.strftime('%p').lower())
else:
time = '{0}:{1}'.format(hr, date.strftime('%M%p').lower())
#calculate the date string
if days_ago == 0:
datestr = 'today {at} {time}'
elif days_ago == 1:
datestr = 'yesterday {at} {time}'
elif (wd in (5, 6) and days_ago in (wd+1, wd+2)) or \
wd + 3 <= days_ago <= wd + 8:
#this was determined by making a table of wd versus days_ago and
#divining a relationship based on everyday speech. This is somewhat
#subjective I guess!
datestr = 'last {weekday} {at} {time} ({days_ago} days ago)'
elif days_ago <= wd + 2:
datestr = '{weekday} {at} {time} ({days_ago} days ago)'
elif years_ago == 1:
datestr = '{month} {day}, {year} {at} {time} (last year)'
elif years_ago > 1:
datestr = '{month} {day}, {year} {at} {time} ({years_ago} years ago)'
elif months_ago == 1:
datestr = '{month} {day} {at} {time} (last month)'
elif months_ago > 1:
datestr = '{month} {day} {at} {time} ({months_ago} months ago)'
else:
#not last week, but not last month either
datestr = '{month} {day} {at} {time} ({days_ago} days ago)'
return datestr.format(time=time,
weekday=date.strftime('%A'),
day=ordinal(date.day),
days=diff.days,
days_ago=days_ago,
month=date.strftime('%B'),
years_ago=years_ago,
months_ago=months_ago,
weeks_ago=weeks_ago,
year=date.year,
at=at)
Written by Josh Kuhn
Related protips
Have a fresh tip? Share with Coderwall community!
Post
Post a tip
Best
#Python
Authors
Sponsored by #native_company# — Learn More
#native_title#
#native_desc#