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
« 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
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
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 = {}
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}
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)
51def reset_format_cache():
52 """Clear any cached formats.
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 = {}
62def iter_format_modules(lang, format_module_path=None):
63 """Find format modules."""
64 if not check_for_language(lang):
65 return
67 if format_module_path is None:
68 format_module_path = settings.FORMAT_MODULE_PATH
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
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]
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'.
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
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
147get_format_lazy = lazy(get_format, str, list, tuple)
150def date_format(value, format=None, use_l10n=None):
151 """
152 Format a datetime.date or datetime.datetime object using a
153 localizable format.
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 )
163def time_format(value, format=None, use_l10n=None):
164 """
165 Format a datetime.time object using a localizable format.
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 )
175def number_format(value, decimal_pos=None, use_l10n=None, force_grouping=False):
176 """
177 Format a numeric value using localization settings.
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 )
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.
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
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
249@functools.lru_cache
250def sanitize_strftime_format(fmt):
251 """
252 Ensure that certain specifiers are correctly padded with leading zeros.
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.
259 FreeBSD, macOS, and Windows do not support explicitly specifying the
260 padding, but return four digit years (with leading zeros) as expected.
262 This function checks whether the %Y produces a correctly padded string and,
263 if not, makes the following substitutions:
265 - %C → %02C
266 - %F → %010F
267 - %G → %04G
268 - %Y → %04Y
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 )
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