mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed #33879 -- Improved timesince handling of long intervals.
This commit is contained in:
		| @@ -1,4 +1,3 @@ | ||||
| import calendar | ||||
| import datetime | ||||
|  | ||||
| from django.utils.html import avoid_wrapping | ||||
| @@ -14,14 +13,16 @@ TIME_STRINGS = { | ||||
|     "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"), | ||||
| ) | ||||
| TIME_STRINGS_KEYS = list(TIME_STRINGS.keys()) | ||||
|  | ||||
| TIME_CHUNKS = [ | ||||
|     60 * 60 * 24 * 7,  # week | ||||
|     60 * 60 * 24,  # day | ||||
|     60 * 60,  # hour | ||||
|     60,  # minute | ||||
| ] | ||||
|  | ||||
| MONTHS_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) | ||||
|  | ||||
|  | ||||
| def timesince(d, now=None, reversed=False, time_strings=None, depth=2): | ||||
| @@ -31,9 +32,16 @@ def timesince(d, now=None, reversed=False, time_strings=None, depth=2): | ||||
|     "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. | ||||
|     Seconds and microseconds are ignored. | ||||
|  | ||||
|     The algorithm takes into account the varying duration of years and months. | ||||
|     There is exactly "1 year, 1 month" between 2013/02/10 and 2014/03/10, | ||||
|     but also between 2007/08/10 and 2008/09/10 despite the delta being 393 days | ||||
|     in the former case and 397 in the latter. | ||||
|  | ||||
|     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. | ||||
| @@ -41,8 +49,9 @@ def timesince(d, now=None, reversed=False, time_strings=None, depth=2): | ||||
|     `depth` is an optional integer to control the number of adjacent time | ||||
|     units returned. | ||||
|  | ||||
|     Adapted from | ||||
|     Originally adapted from | ||||
|     https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since | ||||
|     Modified to improve results for years and months. | ||||
|     """ | ||||
|     if time_strings is None: | ||||
|         time_strings = TIME_STRINGS | ||||
| @@ -60,37 +69,64 @@ def timesince(d, now=None, reversed=False, time_strings=None, depth=2): | ||||
|         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 | ||||
|     # 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: | ||||
|  | ||||
|     # Get years and months. | ||||
|     total_months = (now.year - d.year) * 12 + (now.month - d.month) | ||||
|     if d.day > now.day or (d.day == now.day and d.time() > now.time()): | ||||
|         total_months -= 1 | ||||
|     years, months = divmod(total_months, 12) | ||||
|  | ||||
|     # Calculate the remaining time. | ||||
|     # Create a "pivot" datetime shifted from d by years and months, then use | ||||
|     # that to determine the other parts. | ||||
|     if years or months: | ||||
|         pivot_year = d.year + years | ||||
|         pivot_month = d.month + months | ||||
|         if pivot_month > 12: | ||||
|             pivot_month -= 12 | ||||
|             pivot_year += 1 | ||||
|         pivot = datetime.datetime( | ||||
|             pivot_year, | ||||
|             pivot_month, | ||||
|             min(MONTHS_DAYS[pivot_month - 1], d.day), | ||||
|             d.hour, | ||||
|             d.minute, | ||||
|             d.second, | ||||
|         ) | ||||
|     else: | ||||
|         pivot = d | ||||
|     remaining_time = (now - pivot).total_seconds() | ||||
|     partials = [years, months] | ||||
|     for chunk in TIME_CHUNKS: | ||||
|         count = remaining_time // chunk | ||||
|         partials.append(count) | ||||
|         remaining_time -= chunk * count | ||||
|  | ||||
|     # Find the first non-zero part (if any) and then build the result, until | ||||
|     # depth. | ||||
|     i = 0 | ||||
|     for i, value in enumerate(partials): | ||||
|         if value != 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: | ||||
|     while i < len(TIME_STRINGS_KEYS) and current_depth < depth: | ||||
|         value = partials[i] | ||||
|         if value == 0: | ||||
|             break | ||||
|         result.append(avoid_wrapping(time_strings[name] % {"num": count})) | ||||
|         since -= seconds * count | ||||
|         name = TIME_STRINGS_KEYS[i] | ||||
|         result.append(avoid_wrapping(time_strings[name] % {"num": value})) | ||||
|         current_depth += 1 | ||||
|         i += 1 | ||||
|  | ||||
|     return gettext(", ").join(result) | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user