|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- import calendar
- import datetime
-
- from django.utils.html import avoid_wrapping
- from django.utils.timezone import is_aware
- 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(datetime.timezone.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)
|