Python: I need to show file modification times in the \"1 day ago\", \"two hours ago\", format.
Is there something ready to do that? It should be in English.
This is the gist of @sunil 's post
>>> from datetime import datetime
>>> from dateutil.relativedelta import relativedelta
>>> then = datetime(2003, 9, 17, 20, 54, 47, 282310)
>>> relativedelta(then, datetime.now())
relativedelta(years=-11, months=-3, days=-9, hours=-18, minutes=-17, seconds=-8, microseconds=+912664)
DAY_INCREMENTS = [
[365, "year"],
[30, "month"],
[7, "week"],
[1, "day"],
]
SECOND_INCREMENTS = [
[3600, "hour"],
[60, "minute"],
[1, "second"],
]
def time_ago(dt):
diff = datetime.now() - dt # use timezone.now() or equivalent if `dt` is timezone aware
if diff.days < 0:
return "in the future?!?"
for increment, label in DAY_INCREMENTS:
if diff.days >= increment:
increment_diff = int(diff.days / increment)
return str(increment_diff) + " " + label + plural(increment_diff) + " ago"
for increment, label in SECOND_INCREMENTS:
if diff.seconds >= increment:
increment_diff = int(diff.seconds / increment)
return str(increment_diff) + " " + label + plural(increment_diff) + " ago"
return "just now"
def plural(num):
if num != 1:
return "s"
return ""
I have written a detailed blog post for the solution on http://sunilarora.org/17329071 I am posting a quick snippet here as well.
from datetime import datetime
from dateutil.relativedelta import relativedelta
def get_fancy_time(d, display_full_version = False):
"""Returns a user friendly date format
d: some datetime instace in the past
display_second_unit: True/False
"""
#some helpers lambda's
plural = lambda x: 's' if x > 1 else ''
singular = lambda x: x[:-1]
#convert pluran (years) --> to singular (year)
display_unit = lambda unit, name: '%s %s%s'%(unit, name, plural(unit)) if unit > 0 else ''
#time units we are interested in descending order of significance
tm_units = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']
rdelta = relativedelta(datetime.utcnow(), d) #capture the date difference
for idx, tm_unit in enumerate(tm_units):
first_unit_val = getattr(rdelta, tm_unit)
if first_unit_val > 0:
primary_unit = display_unit(first_unit_val, singular(tm_unit))
if display_full_version and idx < len(tm_units)-1:
next_unit = tm_units[idx + 1]
second_unit_val = getattr(rdelta, next_unit)
if second_unit_val > 0:
secondary_unit = display_unit(second_unit_val, singular(next_unit))
return primary_unit + ', ' + secondary_unit
return primary_unit
return None
The answer Jed Smith linked to is good, and I used it for a year or so, but I think it could be improved in a few ways:
Here's what I came up with:
def PrettyRelativeTime(time_diff_secs):
# Each tuple in the sequence gives the name of a unit, and the number of
# previous units which go into it.
weeks_per_month = 365.242 / 12 / 7
intervals = [('minute', 60), ('hour', 60), ('day', 24), ('week', 7),
('month', weeks_per_month), ('year', 12)]
unit, number = 'second', abs(time_diff_secs)
for new_unit, ratio in intervals:
new_number = float(number) / ratio
# If the new number is too small, don't go to the next unit.
if new_number < 2:
break
unit, number = new_unit, new_number
shown_num = int(number)
return '{} {}'.format(shown_num, unit + ('' if shown_num == 1 else 's'))
Notice how every tuple in intervals
is easy to interpret and check: a 'minute'
is 60
seconds; an 'hour'
is 60
minutes; etc. The only fudge is setting weeks_per_month
to its average value; given the application, that should be fine. (And note that it's clear at a glance that the last three constants multiply out to 365.242, the number of days per year.)
One downside to my function is that it doesn't do anything outside the "## units" pattern: "Yesterday", "just now", etc. are right out. Then again, the original poster didn't ask for these fancy terms, so I prefer my function for its succinctness and the readability of its numerical constants. :)
If you happen to be using Django, then new in version 1.4 is the naturaltime
template filter.
To use it, first add 'django.contrib.humanize'
to your INSTALLED_APPS
setting in settings.py, and {% load humanize %}
into the template you're using the filter in.
Then, in your template, if you have a datetime variable my_date
, you can print its distance from the present by using {{ my_date|naturaltime }}
, which will be rendered as something like 4 minutes ago
.
Other new things in Django 1.4.
Documentation for naturaltime and other filters in the django.contrib.humanize set.
In looking for the same thing with the additional requirement that it handle future dates, I found this: http://pypi.python.org/pypi/py-pretty/1
Example code (from site):
from datetime import datetime, timedelta
now = datetime.now()
hrago = now - timedelta(hours=1)
yesterday = now - timedelta(days=1)
tomorrow = now + timedelta(days=1)
dayafter = now + timedelta(days=2)
import pretty
print pretty.date(now) # 'now'
print pretty.date(hrago) # 'an hour ago'
print pretty.date(hrago, short=True) # '1h ago'
print pretty.date(hrago, asdays=True) # 'today'
print pretty.date(yesterday, short=True) # 'yest'
print pretty.date(tomorrow) # 'tomorrow'