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

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

140 statements  

1import datetime 

2import decimal 

3import functools 

4import re 

5import unicodedata 

6from importlib import import_module 

7 

8from django.conf import settings 

9from django.utils import dateformat, numberformat 

10from django.utils.functional import lazy 

11from django.utils.translation import check_for_language, get_language, to_locale 

12 

13# format_cache is a mapping from (format_type, lang) to the format string. 

14# By using the cache, it is possible to avoid running get_format_modules 

15# repeatedly. 

16_format_cache = {} 

17_format_modules_cache = {} 

18 

19ISO_INPUT_FORMATS = { 

20 "DATE_INPUT_FORMATS": ["%Y-%m-%d"], 

21 "TIME_INPUT_FORMATS": ["%H:%M:%S", "%H:%M:%S.%f", "%H:%M"], 

22 "DATETIME_INPUT_FORMATS": [ 

23 "%Y-%m-%d %H:%M:%S", 

24 "%Y-%m-%d %H:%M:%S.%f", 

25 "%Y-%m-%d %H:%M", 

26 "%Y-%m-%d", 

27 ], 

28} 

29 

30 

31FORMAT_SETTINGS = frozenset( 

32 [ 

33 "DECIMAL_SEPARATOR", 

34 "THOUSAND_SEPARATOR", 

35 "NUMBER_GROUPING", 

36 "FIRST_DAY_OF_WEEK", 

37 "MONTH_DAY_FORMAT", 

38 "TIME_FORMAT", 

39 "DATE_FORMAT", 

40 "DATETIME_FORMAT", 

41 "SHORT_DATE_FORMAT", 

42 "SHORT_DATETIME_FORMAT", 

43 "YEAR_MONTH_FORMAT", 

44 "DATE_INPUT_FORMATS", 

45 "TIME_INPUT_FORMATS", 

46 "DATETIME_INPUT_FORMATS", 

47 ] 

48) 

49 

50 

51def reset_format_cache(): 

52 """Clear any cached formats. 

53 

54 This method is provided primarily for testing purposes, 

55 so that the effects of cached formats can be removed. 

56 """ 

57 global _format_cache, _format_modules_cache 

58 _format_cache = {} 

59 _format_modules_cache = {} 

60 

61 

62def iter_format_modules(lang, format_module_path=None): 

63 """Find format modules.""" 

64 if not check_for_language(lang): 

65 return 

66 

67 if format_module_path is None: 

68 format_module_path = settings.FORMAT_MODULE_PATH 

69 

70 format_locations = [] 

71 if format_module_path: 

72 if isinstance(format_module_path, str): 

73 format_module_path = [format_module_path] 

74 for path in format_module_path: 

75 format_locations.append(path + ".%s") 

76 format_locations.append("django.conf.locale.%s") 

77 locale = to_locale(lang) 

78 locales = [locale] 

79 if "_" in locale: 

80 locales.append(locale.split("_")[0]) 

81 for location in format_locations: 

82 for loc in locales: 

83 try: 

84 yield import_module("%s.formats" % (location % loc)) 

85 except ImportError: 

86 pass 

87 

88 

89def get_format_modules(lang=None): 

90 """Return a list of the format modules found.""" 

91 if lang is None: 

92 lang = get_language() 

93 if lang not in _format_modules_cache: 

94 _format_modules_cache[lang] = list( 

95 iter_format_modules(lang, settings.FORMAT_MODULE_PATH) 

96 ) 

97 return _format_modules_cache[lang] 

98 

99 

100def get_format(format_type, lang=None, use_l10n=None): 

101 """ 

102 For a specific format type, return the format for the current 

103 language (locale). Default to the format in the settings. 

104 format_type is the name of the format, e.g. 'DATE_FORMAT'. 

105 

106 If use_l10n is provided and is not None, it forces the value to 

107 be localized (or not), otherwise it's always localized. 

108 """ 

109 if use_l10n is None: 

110 use_l10n = True 

111 if use_l10n and lang is None: 

112 lang = get_language() 

113 format_type = str(format_type) # format_type may be lazy. 

114 cache_key = (format_type, lang) 

115 try: 

116 return _format_cache[cache_key] 

117 except KeyError: 

118 pass 

119 

120 # The requested format_type has not been cached yet. Try to find it in any 

121 # of the format_modules for the given lang if l10n is enabled. If it's not 

122 # there or if l10n is disabled, fall back to the project settings. 

123 val = None 

124 if use_l10n: 

125 for module in get_format_modules(lang): 

126 val = getattr(module, format_type, None) 

127 if val is not None: 

128 break 

129 if val is None: 

130 if format_type not in FORMAT_SETTINGS: 

131 return format_type 

132 val = getattr(settings, format_type) 

133 elif format_type in ISO_INPUT_FORMATS: 

134 # If a list of input formats from one of the format_modules was 

135 # retrieved, make sure the ISO_INPUT_FORMATS are in this list. 

136 val = list(val) 

137 for iso_input in ISO_INPUT_FORMATS.get(format_type, ()): 

138 if iso_input not in val: 

139 val.append(iso_input) 

140 _format_cache[cache_key] = val 

141 return val 

142 

143 

144get_format_lazy = lazy(get_format, str, list, tuple) 

145 

146 

147def date_format(value, format=None, use_l10n=None): 

148 """ 

149 Format a datetime.date or datetime.datetime object using a 

150 localizable format. 

151 

152 If use_l10n is provided and is not None, that will force the value to 

153 be localized (or not), otherwise it's always localized. 

154 """ 

155 return dateformat.format( 

156 value, get_format(format or "DATE_FORMAT", use_l10n=use_l10n) 

157 ) 

158 

159 

160def time_format(value, format=None, use_l10n=None): 

161 """ 

162 Format a datetime.time object using a localizable format. 

163 

164 If use_l10n is provided and is not None, it forces the value to 

165 be localized (or not), otherwise it's always localized. 

166 """ 

167 return dateformat.time_format( 

168 value, get_format(format or "TIME_FORMAT", use_l10n=use_l10n) 

169 ) 

170 

171 

172def number_format(value, decimal_pos=None, use_l10n=None, force_grouping=False): 

173 """ 

174 Format a numeric value using localization settings. 

175 

176 If use_l10n is provided and is not None, it forces the value to 

177 be localized (or not), otherwise it's always localized. 

178 """ 

179 if use_l10n is None: 

180 use_l10n = True 

181 lang = get_language() if use_l10n else None 

182 return numberformat.format( 

183 value, 

184 get_format("DECIMAL_SEPARATOR", lang, use_l10n=use_l10n), 

185 decimal_pos, 

186 get_format("NUMBER_GROUPING", lang, use_l10n=use_l10n), 

187 get_format("THOUSAND_SEPARATOR", lang, use_l10n=use_l10n), 

188 force_grouping=force_grouping, 

189 use_l10n=use_l10n, 

190 ) 

191 

192 

193def localize(value, use_l10n=None): 

194 """ 

195 Check if value is a localizable type (date, number...) and return it 

196 formatted as a string using current locale format. 

197 

198 If use_l10n is provided and is not None, it forces the value to 

199 be localized (or not), otherwise it's always localized. 

200 """ 

201 if isinstance(value, str): # Handle strings first for performance reasons. 

202 return value 

203 elif isinstance(value, bool): # Make sure booleans don't get treated as numbers 

204 return str(value) 

205 elif isinstance(value, (decimal.Decimal, float, int)): 

206 if use_l10n is False: 

207 return str(value) 

208 return number_format(value, use_l10n=use_l10n) 

209 elif isinstance(value, datetime.datetime): 

210 return date_format(value, "DATETIME_FORMAT", use_l10n=use_l10n) 

211 elif isinstance(value, datetime.date): 

212 return date_format(value, use_l10n=use_l10n) 

213 elif isinstance(value, datetime.time): 

214 return time_format(value, use_l10n=use_l10n) 

215 return value 

216 

217 

218def localize_input(value, default=None): 

219 """ 

220 Check if an input value is a localizable type and return it 

221 formatted with the appropriate formatting string of the current locale. 

222 """ 

223 if isinstance(value, str): # Handle strings first for performance reasons. 

224 return value 

225 elif isinstance(value, bool): # Don't treat booleans as numbers. 

226 return str(value) 

227 elif isinstance(value, (decimal.Decimal, float, int)): 

228 return number_format(value) 

229 elif isinstance(value, datetime.datetime): 

230 format = default or get_format("DATETIME_INPUT_FORMATS")[0] 

231 format = sanitize_strftime_format(format) 

232 return value.strftime(format) 

233 elif isinstance(value, datetime.date): 

234 format = default or get_format("DATE_INPUT_FORMATS")[0] 

235 format = sanitize_strftime_format(format) 

236 return value.strftime(format) 

237 elif isinstance(value, datetime.time): 

238 format = default or get_format("TIME_INPUT_FORMATS")[0] 

239 return value.strftime(format) 

240 return value 

241 

242 

243@functools.lru_cache 

244def sanitize_strftime_format(fmt): 

245 """ 

246 Ensure that certain specifiers are correctly padded with leading zeros. 

247 

248 For years < 1000 specifiers %C, %F, %G, and %Y don't work as expected for 

249 strftime provided by glibc on Linux as they don't pad the year or century 

250 with leading zeros. Support for specifying the padding explicitly is 

251 available, however, which can be used to fix this issue. 

252 

253 FreeBSD, macOS, and Windows do not support explicitly specifying the 

254 padding, but return four digit years (with leading zeros) as expected. 

255 

256 This function checks whether the %Y produces a correctly padded string and, 

257 if not, makes the following substitutions: 

258 

259 - %C → %02C 

260 - %F → %010F 

261 - %G → %04G 

262 - %Y → %04Y 

263 

264 See https://bugs.python.org/issue13305 for more details. 

265 """ 

266 if datetime.date(1, 1, 1).strftime("%Y") == "0001": 

267 return fmt 

268 mapping = {"C": 2, "F": 10, "G": 4, "Y": 4} 

269 return re.sub( 

270 r"((?:^|[^%])(?:%%)*)%([CFGY])", 

271 lambda m: r"%s%%0%s%s" % (m[1], mapping[m[2]], m[2]), 

272 fmt, 

273 ) 

274 

275 

276def sanitize_separators(value): 

277 """ 

278 Sanitize a value according to the current decimal and 

279 thousand separator setting. Used with form field input. 

280 """ 

281 if isinstance(value, str): 

282 parts = [] 

283 decimal_separator = get_format("DECIMAL_SEPARATOR") 

284 if decimal_separator in value: 

285 value, decimals = value.split(decimal_separator, 1) 

286 parts.append(decimals) 

287 if settings.USE_THOUSAND_SEPARATOR: 

288 thousand_sep = get_format("THOUSAND_SEPARATOR") 

289 if ( 

290 thousand_sep == "." 

291 and value.count(".") == 1 

292 and len(value.split(".")[-1]) != 3 

293 ): 

294 # Special case where we suspect a dot meant decimal separator 

295 # (see #22171). 

296 pass 

297 else: 

298 for replacement in { 

299 thousand_sep, 

300 unicodedata.normalize("NFKD", thousand_sep), 

301 }: 

302 value = value.replace(replacement, "") 

303 parts.append(value) 

304 value = ".".join(reversed(parts)) 

305 return value