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

146 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

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), overriding the value of settings.USE_L10N. 

108 """ 

109 if use_l10n is None: 

110 try: 

111 use_l10n = settings._USE_L10N_INTERNAL 

112 except AttributeError: 

113 use_l10n = settings.USE_L10N 

114 if use_l10n and lang is None: 

115 lang = get_language() 

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

117 cache_key = (format_type, lang) 

118 try: 

119 return _format_cache[cache_key] 

120 except KeyError: 

121 pass 

122 

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

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

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

126 val = None 

127 if use_l10n: 

128 for module in get_format_modules(lang): 

129 val = getattr(module, format_type, None) 

130 if val is not None: 

131 break 

132 if val is None: 

133 if format_type not in FORMAT_SETTINGS: 

134 return format_type 

135 val = getattr(settings, format_type) 

136 elif format_type in ISO_INPUT_FORMATS: 

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

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

139 val = list(val) 

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

141 if iso_input not in val: 

142 val.append(iso_input) 

143 _format_cache[cache_key] = val 

144 return val 

145 

146 

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

148 

149 

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

151 """ 

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

153 localizable format. 

154 

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

156 be localized (or not), overriding the value of settings.USE_L10N. 

157 """ 

158 return dateformat.format( 

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

160 ) 

161 

162 

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

164 """ 

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

166 

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

168 be localized (or not), overriding the value of settings.USE_L10N. 

169 """ 

170 return dateformat.time_format( 

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

172 ) 

173 

174 

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

176 """ 

177 Format a numeric value using localization settings. 

178 

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

180 be localized (or not), overriding the value of settings.USE_L10N. 

181 """ 

182 if use_l10n is None: 

183 try: 

184 use_l10n = settings._USE_L10N_INTERNAL 

185 except AttributeError: 

186 use_l10n = settings.USE_L10N 

187 lang = get_language() if use_l10n else None 

188 return numberformat.format( 

189 value, 

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

191 decimal_pos, 

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

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

194 force_grouping=force_grouping, 

195 use_l10n=use_l10n, 

196 ) 

197 

198 

199def localize(value, use_l10n=None): 

200 """ 

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

202 formatted as a string using current locale format. 

203 

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

205 be localized (or not), overriding the value of settings.USE_L10N. 

206 """ 

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

208 return value 

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

210 return str(value) 

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

212 if use_l10n is False: 

213 return str(value) 

214 return number_format(value, use_l10n=use_l10n) 

215 elif isinstance(value, datetime.datetime): 

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

217 elif isinstance(value, datetime.date): 

218 return date_format(value, use_l10n=use_l10n) 

219 elif isinstance(value, datetime.time): 

220 return time_format(value, use_l10n=use_l10n) 

221 return value 

222 

223 

224def localize_input(value, default=None): 

225 """ 

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

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

228 """ 

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

230 return value 

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

232 return str(value) 

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

234 return number_format(value) 

235 elif isinstance(value, datetime.datetime): 

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

237 format = sanitize_strftime_format(format) 

238 return value.strftime(format) 

239 elif isinstance(value, datetime.date): 

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

241 format = sanitize_strftime_format(format) 

242 return value.strftime(format) 

243 elif isinstance(value, datetime.time): 

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

245 return value.strftime(format) 

246 return value 

247 

248 

249@functools.lru_cache 

250def sanitize_strftime_format(fmt): 

251 """ 

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

253 

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

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

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

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

258 

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

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

261 

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

263 if not, makes the following substitutions: 

264 

265 - %C → %02C 

266 - %F → %010F 

267 - %G → %04G 

268 - %Y → %04Y 

269 

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

271 """ 

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

273 return fmt 

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

275 return re.sub( 

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

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

278 fmt, 

279 ) 

280 

281 

282def sanitize_separators(value): 

283 """ 

284 Sanitize a value according to the current decimal and 

285 thousand separator setting. Used with form field input. 

286 """ 

287 if isinstance(value, str): 

288 parts = [] 

289 decimal_separator = get_format("DECIMAL_SEPARATOR") 

290 if decimal_separator in value: 

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

292 parts.append(decimals) 

293 if settings.USE_THOUSAND_SEPARATOR: 

294 thousand_sep = get_format("THOUSAND_SEPARATOR") 

295 if ( 

296 thousand_sep == "." 

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

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

299 ): 

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

301 # (see #22171). 

302 pass 

303 else: 

304 for replacement in { 

305 thousand_sep, 

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

307 }: 

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

309 parts.append(value) 

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

311 return value