Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/utils/timesince.py: 16%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

63 statements  

1import datetime 

2 

3from django.utils.html import avoid_wrapping 

4from django.utils.timezone import is_aware 

5from django.utils.translation import gettext, ngettext_lazy 

6 

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} 

15 

16TIME_STRINGS_KEYS = list(TIME_STRINGS.keys()) 

17 

18TIME_CHUNKS = [ 

19 60 * 60 * 24 * 7, # week 

20 60 * 60 * 24, # day 

21 60 * 60, # hour 

22 60, # minute 

23] 

24 

25MONTHS_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 

26 

27 

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". 

33 

34 Units used are years, months, weeks, days, hours, and minutes. 

35 Seconds and microseconds are ignored. 

36 

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. 

41 

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. 

45 

46 `time_strings` is an optional dict of strings to replace the default 

47 TIME_STRINGS dict. 

48 

49 `depth` is an optional integer to control the number of adjacent time 

50 units returned. 

51 

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) 

65 

66 # Compared datetimes must be in the same time zone. 

67 if not now: 

68 now = datetime.datetime.now(d.tzinfo if is_aware(d) else None) 

69 elif is_aware(now) and is_aware(d): 

70 now = now.astimezone(d.tzinfo) 

71 

72 if reversed: 

73 d, now = now, d 

74 delta = now - d 

75 

76 # Ignore microseconds. 

77 since = delta.days * 24 * 60 * 60 + delta.seconds 

78 if since <= 0: 

79 # d is in the future compared to now, stop processing. 

80 return avoid_wrapping(time_strings["minute"] % {"num": 0}) 

81 

82 # Get years and months. 

83 total_months = (now.year - d.year) * 12 + (now.month - d.month) 

84 if d.day > now.day or (d.day == now.day and d.time() > now.time()): 

85 total_months -= 1 

86 years, months = divmod(total_months, 12) 

87 

88 # Calculate the remaining time. 

89 # Create a "pivot" datetime shifted from d by years and months, then use 

90 # that to determine the other parts. 

91 if years or months: 

92 pivot_year = d.year + years 

93 pivot_month = d.month + months 

94 if pivot_month > 12: 

95 pivot_month -= 12 

96 pivot_year += 1 

97 pivot = datetime.datetime( 

98 pivot_year, 

99 pivot_month, 

100 min(MONTHS_DAYS[pivot_month - 1], d.day), 

101 d.hour, 

102 d.minute, 

103 d.second, 

104 tzinfo=d.tzinfo, 

105 ) 

106 else: 

107 pivot = d 

108 remaining_time = (now - pivot).total_seconds() 

109 partials = [years, months] 

110 for chunk in TIME_CHUNKS: 

111 count = int(remaining_time // chunk) 

112 partials.append(count) 

113 remaining_time -= chunk * count 

114 

115 # Find the first non-zero part (if any) and then build the result, until 

116 # depth. 

117 i = 0 

118 for i, value in enumerate(partials): 

119 if value != 0: 

120 break 

121 else: 

122 return avoid_wrapping(time_strings["minute"] % {"num": 0}) 

123 

124 result = [] 

125 current_depth = 0 

126 while i < len(TIME_STRINGS_KEYS) and current_depth < depth: 

127 value = partials[i] 

128 if value == 0: 

129 break 

130 name = TIME_STRINGS_KEYS[i] 

131 result.append(avoid_wrapping(time_strings[name] % {"num": value})) 

132 current_depth += 1 

133 i += 1 

134 

135 return gettext(", ").join(result) 

136 

137 

138def timeuntil(d, now=None, time_strings=None, depth=2): 

139 """ 

140 Like timesince, but return a string measuring the time until the given time. 

141 """ 

142 return timesince(d, now, reversed=True, time_strings=time_strings, depth=depth)