168 lines
5.5 KiB
Python
168 lines
5.5 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""Time humanizing functions. These are largely borrowed from Django's
|
|
``contrib.humanize``."""
|
|
|
|
import time
|
|
from datetime import datetime, timedelta, date
|
|
from .i18n import ngettext, gettext as _
|
|
|
|
__all__ = ['naturaldelta', 'naturaltime', 'naturalday', 'naturaldate']
|
|
|
|
def _now():
|
|
return datetime.now()
|
|
|
|
def abs_timedelta(delta):
|
|
"""Returns an "absolute" value for a timedelta, always representing a
|
|
time distance."""
|
|
if delta.days < 0:
|
|
now = _now()
|
|
return now - (now + delta)
|
|
return delta
|
|
|
|
def date_and_delta(value):
|
|
"""Turn a value into a date and a timedelta which represents how long ago
|
|
it was. If that's not possible, return (None, value)."""
|
|
now = _now()
|
|
if isinstance(value, datetime):
|
|
date = value
|
|
delta = now - value
|
|
elif isinstance(value, timedelta):
|
|
date = now - value
|
|
delta = value
|
|
else:
|
|
try:
|
|
value = int(value)
|
|
delta = timedelta(seconds=value)
|
|
date = now - delta
|
|
except (ValueError, TypeError):
|
|
return (None, value)
|
|
return date, abs_timedelta(delta)
|
|
|
|
def naturaldelta(value, months=True):
|
|
"""Given a timedelta or a number of seconds, return a natural
|
|
representation of the amount of time elapsed. This is similar to
|
|
``naturaltime``, but does not add tense to the result. If ``months``
|
|
is True, then a number of months (based on 30.5 days) will be used
|
|
for fuzziness between years."""
|
|
now = _now()
|
|
date, delta = date_and_delta(value)
|
|
if date is None:
|
|
return value
|
|
|
|
use_months = months
|
|
|
|
seconds = abs(delta.seconds)
|
|
days = abs(delta.days)
|
|
years = days // 365
|
|
days = days % 365
|
|
months = int(days // 30.5)
|
|
|
|
if not years and days < 1:
|
|
if seconds == 0:
|
|
return _("a moment")
|
|
elif seconds == 1:
|
|
return _("a second")
|
|
elif seconds < 60:
|
|
return ngettext("%d second", "%d seconds", seconds) % seconds
|
|
elif 60 <= seconds < 120:
|
|
return _("a minute")
|
|
elif 120 <= seconds < 3600:
|
|
minutes = seconds // 60
|
|
return ngettext("%d minute", "%d minutes", minutes) % minutes
|
|
elif 3600 <= seconds < 3600 * 2:
|
|
return _("an hour")
|
|
elif 3600 < seconds:
|
|
hours = seconds // 3600
|
|
return ngettext("%d hour", "%d hours", hours) % hours
|
|
elif years == 0:
|
|
if days == 1:
|
|
return _("a day")
|
|
if not use_months:
|
|
return ngettext("%d day", "%d days", days) % days
|
|
else:
|
|
if not months:
|
|
return ngettext("%d day", "%d days", days) % days
|
|
elif months == 1:
|
|
return _("a month")
|
|
else:
|
|
return ngettext("%d month", "%d months", months) % months
|
|
elif years == 1:
|
|
if not months and not days:
|
|
return _("a year")
|
|
elif not months:
|
|
return ngettext("1 year, %d day", "1 year, %d days", days) % days
|
|
elif use_months:
|
|
if months == 1:
|
|
return _("1 year, 1 month")
|
|
else:
|
|
return ngettext("1 year, %d month",
|
|
"1 year, %d months", months) % months
|
|
else:
|
|
return ngettext("1 year, %d day", "1 year, %d days", days) % days
|
|
else:
|
|
return ngettext("%d year", "%d years", years) % years
|
|
|
|
|
|
def naturaltime(value, future=False, months=True):
|
|
"""Given a datetime or a number of seconds, return a natural representation
|
|
of that time in a resolution that makes sense. This is more or less
|
|
compatible with Django's ``naturaltime`` filter. ``future`` is ignored for
|
|
datetimes, where the tense is always figured out based on the current time.
|
|
If an integer is passed, the return value will be past tense by default,
|
|
unless ``future`` is set to True."""
|
|
now = _now()
|
|
date, delta = date_and_delta(value)
|
|
if date is None:
|
|
return value
|
|
# determine tense by value only if datetime/timedelta were passed
|
|
if isinstance(value, (datetime, timedelta)):
|
|
future = date > now
|
|
|
|
ago = _('%s from now') if future else _('%s ago')
|
|
delta = naturaldelta(delta, months)
|
|
|
|
if delta == _("a moment"):
|
|
return _("now")
|
|
|
|
return ago % delta
|
|
|
|
def naturalday(value, format='%b %d'):
|
|
"""For date values that are tomorrow, today or yesterday compared to
|
|
present day returns representing string. Otherwise, returns a string
|
|
formatted according to ``format``."""
|
|
try:
|
|
value = date(value.year, value.month, value.day)
|
|
except AttributeError:
|
|
# Passed value wasn't date-ish
|
|
return value
|
|
except (OverflowError, ValueError):
|
|
# Date arguments out of range
|
|
return value
|
|
delta = value - date.today()
|
|
if delta.days == 0:
|
|
return _('today')
|
|
elif delta.days == 1:
|
|
return _('tomorrow')
|
|
elif delta.days == -1:
|
|
return _('yesterday')
|
|
return value.strftime(format)
|
|
|
|
def naturaldate(value):
|
|
"""Like naturalday, but will append a year for dates that are a year
|
|
ago or more."""
|
|
try:
|
|
value = date(value.year, value.month, value.day)
|
|
except AttributeError:
|
|
# Passed value wasn't date-ish
|
|
return value
|
|
except (OverflowError, ValueError):
|
|
# Date arguments out of range
|
|
return value
|
|
delta = abs_timedelta(value - date.today())
|
|
if delta.days >= 365:
|
|
return naturalday(value, '%b %d %Y')
|
|
return naturalday(value)
|
|
|
|
|