123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- import datetime
- import decimal
- import unicodedata
- from importlib import import_module
-
- from django.conf import settings
- from django.utils import dateformat, datetime_safe, 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, reverse=False):
- """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))
- modules = _format_modules_cache[lang]
- if reverse:
- return list(reversed(modules))
- return modules
-
-
- 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.
- """
- use_l10n = use_l10n or (use_l10n is None and settings.USE_L10N)
- if use_l10n and lang is None:
- lang = get_language()
- 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 or (use_l10n is None and settings.USE_L10N):
- lang = get_language()
- else:
- lang = 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)):
- 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):
- value = datetime_safe.new_datetime(value)
- format = default or get_format('DATETIME_INPUT_FORMATS')[0]
- return value.strftime(format)
- elif isinstance(value, datetime.date):
- value = datetime_safe.new_date(value)
- format = default or get_format('DATE_INPUT_FORMATS')[0]
- return value.strftime(format)
- elif isinstance(value, datetime.time):
- format = default or get_format('TIME_INPUT_FORMATS')[0]
- return value.strftime(format)
- return value
-
-
- 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
|