django1/django/utils/timesince.py

102 lines
3.5 KiB
Python

import calendar
import datetime
from django.utils.html import avoid_wrapping
from django.utils.timezone import is_aware, utc
from django.utils.translation import gettext, ngettext_lazy
TIME_STRINGS = {
'year': ngettext_lazy('%(num)d year', '%(num)d years', 'num'),
'month': ngettext_lazy('%(num)d month', '%(num)d months', 'num'),
'week': ngettext_lazy('%(num)d week', '%(num)d weeks', 'num'),
'day': ngettext_lazy('%(num)d day', '%(num)d days', 'num'),
'hour': ngettext_lazy('%(num)d hour', '%(num)d hours', 'num'),
'minute': ngettext_lazy('%(num)d minute', '%(num)d minutes', 'num'),
}
TIMESINCE_CHUNKS = (
(60 * 60 * 24 * 365, 'year'),
(60 * 60 * 24 * 30, 'month'),
(60 * 60 * 24 * 7, 'week'),
(60 * 60 * 24, 'day'),
(60 * 60, 'hour'),
(60, 'minute'),
)
def timesince(d, now=None, reversed=False, time_strings=None, depth=2):
"""
Take two datetime objects and return the time between d and now as a nicely
formatted string, e.g. "10 minutes". If d occurs after now, return
"0 minutes".
Units used are years, months, weeks, days, hours, and minutes.
Seconds and microseconds are ignored. Up to `depth` adjacent units will be
displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
`time_strings` is an optional dict of strings to replace the default
TIME_STRINGS dict.
`depth` is an optional integer to control the number of adjacent time
units returned.
Adapted from
https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
"""
if time_strings is None:
time_strings = TIME_STRINGS
if depth <= 0:
raise ValueError('depth must be greater than 0.')
# Convert datetime.date to datetime.datetime for comparison.
if not isinstance(d, datetime.datetime):
d = datetime.datetime(d.year, d.month, d.day)
if now and not isinstance(now, datetime.datetime):
now = datetime.datetime(now.year, now.month, now.day)
now = now or datetime.datetime.now(utc if is_aware(d) else None)
if reversed:
d, now = now, d
delta = now - d
# Deal with leapyears by subtracing the number of leapdays
leapdays = calendar.leapdays(d.year, now.year)
if leapdays != 0:
if calendar.isleap(d.year):
leapdays -= 1
elif calendar.isleap(now.year):
leapdays += 1
delta -= datetime.timedelta(leapdays)
# ignore microseconds
since = delta.days * 24 * 60 * 60 + delta.seconds
if since <= 0:
# d is in the future compared to now, stop processing.
return avoid_wrapping(time_strings['minute'] % {'num': 0})
for i, (seconds, name) in enumerate(TIMESINCE_CHUNKS):
count = since // seconds
if count != 0:
break
else:
return avoid_wrapping(time_strings['minute'] % {'num': 0})
result = []
current_depth = 0
while i < len(TIMESINCE_CHUNKS) and current_depth < depth:
seconds, name = TIMESINCE_CHUNKS[i]
count = since // seconds
if count == 0:
break
result.append(avoid_wrapping(time_strings[name] % {'num': count}))
since -= seconds * count
current_depth += 1
i += 1
return gettext(', ').join(result)
def timeuntil(d, now=None, time_strings=None, depth=2):
"""
Like timesince, but return a string measuring the time until the given time.
"""
return timesince(d, now, reversed=True, time_strings=time_strings, depth=depth)