Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/utils/timesince.py: 17%
60 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1import datetime
3from django.utils.html import avoid_wrapping
4from django.utils.timezone import is_aware
5from django.utils.translation import gettext, ngettext_lazy
7TIME_STRINGS = {
8 "year": ngettext_lazy("%(num)d year", "%(num)d years", "num"),
9 "month": ngettext_lazy("%(num)d month", "%(num)d months", "num"),
10 "week": ngettext_lazy("%(num)d week", "%(num)d weeks", "num"),
11 "day": ngettext_lazy("%(num)d day", "%(num)d days", "num"),
12 "hour": ngettext_lazy("%(num)d hour", "%(num)d hours", "num"),
13 "minute": ngettext_lazy("%(num)d minute", "%(num)d minutes", "num"),
14}
16TIME_STRINGS_KEYS = list(TIME_STRINGS.keys())
18TIME_CHUNKS = [
19 60 * 60 * 24 * 7, # week
20 60 * 60 * 24, # day
21 60 * 60, # hour
22 60, # minute
23]
25MONTHS_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
28def timesince(d, now=None, reversed=False, time_strings=None, depth=2):
29 """
30 Take two datetime objects and return the time between d and now as a nicely
31 formatted string, e.g. "10 minutes". If d occurs after now, return
32 "0 minutes".
34 Units used are years, months, weeks, days, hours, and minutes.
35 Seconds and microseconds are ignored.
37 The algorithm takes into account the varying duration of years and months.
38 There is exactly "1 year, 1 month" between 2013/02/10 and 2014/03/10,
39 but also between 2007/08/10 and 2008/09/10 despite the delta being 393 days
40 in the former case and 397 in the latter.
42 Up to `depth` adjacent units will be displayed. For example,
43 "2 weeks, 3 days" and "1 year, 3 months" are possible outputs, but
44 "2 weeks, 3 hours" and "1 year, 5 days" are not.
46 `time_strings` is an optional dict of strings to replace the default
47 TIME_STRINGS dict.
49 `depth` is an optional integer to control the number of adjacent time
50 units returned.
52 Originally adapted from
53 https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
54 Modified to improve results for years and months.
55 """
56 if time_strings is None:
57 time_strings = TIME_STRINGS
58 if depth <= 0:
59 raise ValueError("depth must be greater than 0.")
60 # Convert datetime.date to datetime.datetime for comparison.
61 if not isinstance(d, datetime.datetime):
62 d = datetime.datetime(d.year, d.month, d.day)
63 if now and not isinstance(now, datetime.datetime):
64 now = datetime.datetime(now.year, now.month, now.day)
66 now = now or datetime.datetime.now(datetime.timezone.utc if is_aware(d) else None)
68 if reversed:
69 d, now = now, d
70 delta = now - d
72 # Ignore microseconds.
73 since = delta.days * 24 * 60 * 60 + delta.seconds
74 if since <= 0:
75 # d is in the future compared to now, stop processing.
76 return avoid_wrapping(time_strings["minute"] % {"num": 0})
78 # Get years and months.
79 total_months = (now.year - d.year) * 12 + (now.month - d.month)
80 if d.day > now.day or (d.day == now.day and d.time() > now.time()):
81 total_months -= 1
82 years, months = divmod(total_months, 12)
84 # Calculate the remaining time.
85 # Create a "pivot" datetime shifted from d by years and months, then use
86 # that to determine the other parts.
87 if years or months:
88 pivot_year = d.year + years
89 pivot_month = d.month + months
90 if pivot_month > 12:
91 pivot_month -= 12
92 pivot_year += 1
93 pivot = datetime.datetime(
94 pivot_year,
95 pivot_month,
96 min(MONTHS_DAYS[pivot_month - 1], d.day),
97 d.hour,
98 d.minute,
99 d.second,
100 tzinfo=d.tzinfo,
101 )
102 else:
103 pivot = d
104 remaining_time = (now - pivot).total_seconds()
105 partials = [years, months]
106 for chunk in TIME_CHUNKS:
107 count = int(remaining_time // chunk)
108 partials.append(count)
109 remaining_time -= chunk * count
111 # Find the first non-zero part (if any) and then build the result, until
112 # depth.
113 i = 0
114 for i, value in enumerate(partials):
115 if value != 0:
116 break
117 else:
118 return avoid_wrapping(time_strings["minute"] % {"num": 0})
120 result = []
121 current_depth = 0
122 while i < len(TIME_STRINGS_KEYS) and current_depth < depth:
123 value = partials[i]
124 if value == 0:
125 break
126 name = TIME_STRINGS_KEYS[i]
127 result.append(avoid_wrapping(time_strings[name] % {"num": value}))
128 current_depth += 1
129 i += 1
131 return gettext(", ").join(result)
134def timeuntil(d, now=None, time_strings=None, depth=2):
135 """
136 Like timesince, but return a string measuring the time until the given time.
137 """
138 return timesince(d, now, reversed=True, time_strings=time_strings, depth=depth)