|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- import datetime
- import decimal
- import functools
- import re
- import unicodedata
- from importlib import import_module
-
- from django.conf import settings
- from django.utils import dateformat, numberformat
- from django.utils.functional import lazy
- from django.utils.translation import check_for_language, get_language, to_locale
-
- # format_cache is a mapping from (format_type, lang) to the format string.
- # By using the cache, it is possible to avoid running get_format_modules
- # repeatedly.
- _format_cache = {}
- _format_modules_cache = {}
-
- ISO_INPUT_FORMATS = {
- "DATE_INPUT_FORMATS": ["%Y-%m-%d"],
- "TIME_INPUT_FORMATS": ["%H:%M:%S", "%H:%M:%S.%f", "%H:%M"],
- "DATETIME_INPUT_FORMATS": [
- "%Y-%m-%d %H:%M:%S",
- "%Y-%m-%d %H:%M:%S.%f",
- "%Y-%m-%d %H:%M",
- "%Y-%m-%d",
- ],
- }
-
-
- FORMAT_SETTINGS = frozenset(
- [
- "DECIMAL_SEPARATOR",
- "THOUSAND_SEPARATOR",
- "NUMBER_GROUPING",
- "FIRST_DAY_OF_WEEK",
- "MONTH_DAY_FORMAT",
- "TIME_FORMAT",
- "DATE_FORMAT",
- "DATETIME_FORMAT",
- "SHORT_DATE_FORMAT",
- "SHORT_DATETIME_FORMAT",
- "YEAR_MONTH_FORMAT",
- "DATE_INPUT_FORMATS",
- "TIME_INPUT_FORMATS",
- "DATETIME_INPUT_FORMATS",
- ]
- )
-
-
- def reset_format_cache():
- """Clear any cached formats.
-
- This method is provided primarily for testing purposes,
- so that the effects of cached formats can be removed.
- """
- global _format_cache, _format_modules_cache
- _format_cache = {}
- _format_modules_cache = {}
-
-
- def iter_format_modules(lang, format_module_path=None):
- """Find format modules."""
- if not check_for_language(lang):
- return
-
- if format_module_path is None:
- format_module_path = settings.FORMAT_MODULE_PATH
-
- format_locations = []
- if format_module_path:
- if isinstance(format_module_path, str):
- format_module_path = [format_module_path]
- for path in format_module_path:
- format_locations.append(path + ".%s")
- format_locations.append("django.conf.locale.%s")
- locale = to_locale(lang)
- locales = [locale]
- if "_" in locale:
- locales.append(locale.split("_")[0])
- for location in format_locations:
- for loc in locales:
- try:
- yield import_module("%s.formats" % (location % loc))
- except ImportError:
- pass
-
-
- def get_format_modules(lang=None):
- """Return a list of the format modules found."""
- if lang is None:
- lang = get_language()
- if lang not in _format_modules_cache:
- _format_modules_cache[lang] = list(
- iter_format_modules(lang, settings.FORMAT_MODULE_PATH)
- )
- return _format_modules_cache[lang]
-
-
- def get_format(format_type, lang=None, use_l10n=None):
- """
- For a specific format type, return the format for the current
- language (locale). Default to the format in the settings.
- format_type is the name of the format, e.g. 'DATE_FORMAT'.
-
- If use_l10n is provided and is not None, it forces the value to
- be localized (or not), overriding the value of settings.USE_L10N.
- """
- if use_l10n is None:
- try:
- use_l10n = settings._USE_L10N_INTERNAL
- except AttributeError:
- use_l10n = settings.USE_L10N
- if use_l10n and lang is None:
- lang = get_language()
- format_type = str(format_type) # format_type may be lazy.
- cache_key = (format_type, lang)
- try:
- return _format_cache[cache_key]
- except KeyError:
- pass
-
- # The requested format_type has not been cached yet. Try to find it in any
- # of the format_modules for the given lang if l10n is enabled. If it's not
- # there or if l10n is disabled, fall back to the project settings.
- val = None
- if use_l10n:
- for module in get_format_modules(lang):
- val = getattr(module, format_type, None)
- if val is not None:
- break
- if val is None:
- if format_type not in FORMAT_SETTINGS:
- return format_type
- val = getattr(settings, format_type)
- elif format_type in ISO_INPUT_FORMATS:
- # If a list of input formats from one of the format_modules was
- # retrieved, make sure the ISO_INPUT_FORMATS are in this list.
- val = list(val)
- for iso_input in ISO_INPUT_FORMATS.get(format_type, ()):
- if iso_input not in val:
- val.append(iso_input)
- _format_cache[cache_key] = val
- return val
-
-
- get_format_lazy = lazy(get_format, str, list, tuple)
-
-
- def date_format(value, format=None, use_l10n=None):
- """
- Format a datetime.date or datetime.datetime object using a
- localizable format.
-
- If use_l10n is provided and is not None, that will force the value to
- be localized (or not), overriding the value of settings.USE_L10N.
- """
- return dateformat.format(
- value, get_format(format or "DATE_FORMAT", use_l10n=use_l10n)
- )
-
-
- def time_format(value, format=None, use_l10n=None):
- """
- Format a datetime.time object using a localizable format.
-
- If use_l10n is provided and is not None, it forces the value to
- be localized (or not), overriding the value of settings.USE_L10N.
- """
- return dateformat.time_format(
- value, get_format(format or "TIME_FORMAT", use_l10n=use_l10n)
- )
-
-
- def number_format(value, decimal_pos=None, use_l10n=None, force_grouping=False):
- """
- Format a numeric value using localization settings.
-
- If use_l10n is provided and is not None, it forces the value to
- be localized (or not), overriding the value of settings.USE_L10N.
- """
- if use_l10n is None:
- try:
- use_l10n = settings._USE_L10N_INTERNAL
- except AttributeError:
- use_l10n = settings.USE_L10N
- lang = get_language() if use_l10n else None
- return numberformat.format(
- value,
- get_format("DECIMAL_SEPARATOR", lang, use_l10n=use_l10n),
- decimal_pos,
- get_format("NUMBER_GROUPING", lang, use_l10n=use_l10n),
- get_format("THOUSAND_SEPARATOR", lang, use_l10n=use_l10n),
- force_grouping=force_grouping,
- use_l10n=use_l10n,
- )
-
-
- def localize(value, use_l10n=None):
- """
- Check if value is a localizable type (date, number...) and return it
- formatted as a string using current locale format.
-
- If use_l10n is provided and is not None, it forces the value to
- be localized (or not), overriding the value of settings.USE_L10N.
- """
- if isinstance(value, str): # Handle strings first for performance reasons.
- return value
- elif isinstance(value, bool): # Make sure booleans don't get treated as numbers
- return str(value)
- elif isinstance(value, (decimal.Decimal, float, int)):
- if use_l10n is False:
- return str(value)
- return number_format(value, use_l10n=use_l10n)
- elif isinstance(value, datetime.datetime):
- return date_format(value, "DATETIME_FORMAT", use_l10n=use_l10n)
- elif isinstance(value, datetime.date):
- return date_format(value, use_l10n=use_l10n)
- elif isinstance(value, datetime.time):
- return time_format(value, "TIME_FORMAT", use_l10n=use_l10n)
- return value
-
-
- def localize_input(value, default=None):
- """
- Check if an input value is a localizable type and return it
- formatted with the appropriate formatting string of the current locale.
- """
- if isinstance(value, str): # Handle strings first for performance reasons.
- return value
- elif isinstance(value, bool): # Don't treat booleans as numbers.
- return str(value)
- elif isinstance(value, (decimal.Decimal, float, int)):
- return number_format(value)
- elif isinstance(value, datetime.datetime):
- format = default or get_format("DATETIME_INPUT_FORMATS")[0]
- format = sanitize_strftime_format(format)
- return value.strftime(format)
- elif isinstance(value, datetime.date):
- format = default or get_format("DATE_INPUT_FORMATS")[0]
- format = sanitize_strftime_format(format)
- return value.strftime(format)
- elif isinstance(value, datetime.time):
- format = default or get_format("TIME_INPUT_FORMATS")[0]
- return value.strftime(format)
- return value
-
-
- @functools.lru_cache
- def sanitize_strftime_format(fmt):
- """
- Ensure that certain specifiers are correctly padded with leading zeros.
-
- For years < 1000 specifiers %C, %F, %G, and %Y don't work as expected for
- strftime provided by glibc on Linux as they don't pad the year or century
- with leading zeros. Support for specifying the padding explicitly is
- available, however, which can be used to fix this issue.
-
- FreeBSD, macOS, and Windows do not support explicitly specifying the
- padding, but return four digit years (with leading zeros) as expected.
-
- This function checks whether the %Y produces a correctly padded string and,
- if not, makes the following substitutions:
-
- - %C → %02C
- - %F → %010F
- - %G → %04G
- - %Y → %04Y
-
- See https://bugs.python.org/issue13305 for more details.
- """
- if datetime.date(1, 1, 1).strftime("%Y") == "0001":
- return fmt
- mapping = {"C": 2, "F": 10, "G": 4, "Y": 4}
- return re.sub(
- r"((?:^|[^%])(?:%%)*)%([CFGY])",
- lambda m: r"%s%%0%s%s" % (m[1], mapping[m[2]], m[2]),
- fmt,
- )
-
-
- def sanitize_separators(value):
- """
- Sanitize a value according to the current decimal and
- thousand separator setting. Used with form field input.
- """
- if isinstance(value, str):
- parts = []
- decimal_separator = get_format("DECIMAL_SEPARATOR")
- if decimal_separator in value:
- value, decimals = value.split(decimal_separator, 1)
- parts.append(decimals)
- if settings.USE_THOUSAND_SEPARATOR:
- thousand_sep = get_format("THOUSAND_SEPARATOR")
- if (
- thousand_sep == "."
- and value.count(".") == 1
- and len(value.split(".")[-1]) != 3
- ):
- # Special case where we suspect a dot meant decimal separator
- # (see #22171).
- pass
- else:
- for replacement in {
- thousand_sep,
- unicodedata.normalize("NFKD", thousand_sep),
- }:
- value = value.replace(replacement, "")
- parts.append(value)
- value = ".".join(reversed(parts))
- return value
|