Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pendulum/formatting/difference_formatter.py: 22%

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

97 statements  

1from __future__ import annotations 

2 

3import typing as t 

4 

5from pendulum.locales.locale import Locale 

6 

7 

8if t.TYPE_CHECKING: 

9 from pendulum import Duration 

10 

11DAYS_THRESHOLD_FOR_HALF_WEEK = 3 

12DAYS_THRESHOLD_FOR_HALF_MONTH = 15 

13MONTHS_THRESHOLD_FOR_HALF_YEAR = 6 

14 

15HOURS_IN_NEARLY_A_DAY = 22 

16DAYS_IN_NEARLY_A_MONTH = 27 

17MONTHS_IN_NEARLY_A_YEAR = 11 

18 

19DAYS_OF_WEEK = 7 

20SECONDS_OF_MINUTE = 60 

21FEW_SECONDS_MAX = 10 

22 

23KEY_FUTURE = ".future" 

24KEY_PAST = ".past" 

25KEY_AFTER = ".after" 

26KEY_BEFORE = ".before" 

27 

28 

29class DifferenceFormatter: 

30 """ 

31 Handles formatting differences in text. 

32 """ 

33 

34 def __init__(self, locale: str = "en") -> None: 

35 self._locale = Locale.load(locale) 

36 

37 def format( 

38 self, 

39 diff: Duration, 

40 is_now: bool = True, 

41 absolute: bool = False, 

42 locale: str | Locale | None = None, 

43 ) -> str: 

44 """ 

45 Formats a difference. 

46 

47 :param diff: The difference to format 

48 :param is_now: Whether the difference includes now 

49 :param absolute: Whether it's an absolute difference or not 

50 :param locale: The locale to use 

51 """ 

52 locale = self._locale if locale is None else Locale.load(locale) 

53 

54 if diff.years > 0: 

55 unit = "year" 

56 count = diff.years 

57 

58 if diff.months > MONTHS_THRESHOLD_FOR_HALF_YEAR: 

59 count += 1 

60 elif (diff.months == MONTHS_IN_NEARLY_A_YEAR) and ( 

61 (diff.weeks * DAYS_OF_WEEK + diff.remaining_days) 

62 > DAYS_THRESHOLD_FOR_HALF_MONTH 

63 ): 

64 unit = "year" 

65 count = 1 

66 elif diff.months > 0: 

67 unit = "month" 

68 count = diff.months 

69 

70 if ( 

71 diff.weeks * DAYS_OF_WEEK + diff.remaining_days 

72 ) >= DAYS_IN_NEARLY_A_MONTH: 

73 count += 1 

74 elif diff.weeks > 0: 

75 unit = "week" 

76 count = diff.weeks 

77 

78 if diff.remaining_days > DAYS_THRESHOLD_FOR_HALF_WEEK: 

79 count += 1 

80 elif diff.remaining_days > 0: 

81 unit = "day" 

82 count = diff.remaining_days 

83 

84 if diff.hours >= HOURS_IN_NEARLY_A_DAY: 

85 count += 1 

86 elif diff.hours > 0: 

87 unit = "hour" 

88 count = diff.hours 

89 elif diff.minutes > 0: 

90 unit = "minute" 

91 count = diff.minutes 

92 elif FEW_SECONDS_MAX < diff.remaining_seconds < SECONDS_OF_MINUTE: 

93 unit = "second" 

94 count = diff.remaining_seconds 

95 else: 

96 # We check if the "a few seconds" unit exists 

97 time = locale.get("custom.units.few_second") 

98 if time is not None: 

99 if absolute: 

100 return t.cast("str", time) 

101 

102 key = "custom" 

103 is_future = diff.invert 

104 if is_now: 

105 if is_future: 

106 key += ".from_now" 

107 else: 

108 key += ".ago" 

109 else: 

110 if is_future: 

111 key += KEY_AFTER 

112 else: 

113 key += KEY_BEFORE 

114 

115 return t.cast("str", locale.get(key).format(time)) 

116 else: 

117 unit = "second" 

118 count = diff.remaining_seconds 

119 if count == 0: 

120 count = 1 

121 if absolute: 

122 key = f"translations.units.{unit}" 

123 else: 

124 is_future = diff.invert 

125 if is_now: 

126 # Relative to now, so we can use 

127 # the CLDR data 

128 key = f"translations.relative.{unit}" 

129 

130 if is_future: 

131 key += KEY_FUTURE 

132 else: 

133 key += KEY_PAST 

134 else: 

135 # Absolute comparison 

136 # So we have to use the custom locale data 

137 

138 # Checking for special pluralization rules 

139 key = "custom.units_relative" 

140 if is_future: 

141 key += f".{unit}{KEY_FUTURE}" 

142 else: 

143 key += f".{unit}{KEY_PAST}" 

144 

145 trans = locale.get(key) 

146 if not trans: 

147 # No special rule 

148 key = f"translations.units.{unit}.{locale.plural(count)}" 

149 time = locale.get(key).format(count) 

150 else: 

151 time = trans[locale.plural(count)].format(count) 

152 

153 key = "custom" 

154 if is_future: 

155 key += KEY_AFTER 

156 else: 

157 key += KEY_BEFORE 

158 

159 return t.cast("str", locale.get(key).format(time)) 

160 

161 key += f".{locale.plural(count)}" 

162 

163 return t.cast("str", locale.get(key).format(count))