Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

formats.py 10KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import datetime
  2. import decimal
  3. import functools
  4. import re
  5. import unicodedata
  6. from importlib import import_module
  7. from django.conf import settings
  8. from django.utils import dateformat, numberformat
  9. from django.utils.functional import lazy
  10. from django.utils.translation import check_for_language, get_language, to_locale
  11. # format_cache is a mapping from (format_type, lang) to the format string.
  12. # By using the cache, it is possible to avoid running get_format_modules
  13. # repeatedly.
  14. _format_cache = {}
  15. _format_modules_cache = {}
  16. ISO_INPUT_FORMATS = {
  17. "DATE_INPUT_FORMATS": ["%Y-%m-%d"],
  18. "TIME_INPUT_FORMATS": ["%H:%M:%S", "%H:%M:%S.%f", "%H:%M"],
  19. "DATETIME_INPUT_FORMATS": [
  20. "%Y-%m-%d %H:%M:%S",
  21. "%Y-%m-%d %H:%M:%S.%f",
  22. "%Y-%m-%d %H:%M",
  23. "%Y-%m-%d",
  24. ],
  25. }
  26. FORMAT_SETTINGS = frozenset(
  27. [
  28. "DECIMAL_SEPARATOR",
  29. "THOUSAND_SEPARATOR",
  30. "NUMBER_GROUPING",
  31. "FIRST_DAY_OF_WEEK",
  32. "MONTH_DAY_FORMAT",
  33. "TIME_FORMAT",
  34. "DATE_FORMAT",
  35. "DATETIME_FORMAT",
  36. "SHORT_DATE_FORMAT",
  37. "SHORT_DATETIME_FORMAT",
  38. "YEAR_MONTH_FORMAT",
  39. "DATE_INPUT_FORMATS",
  40. "TIME_INPUT_FORMATS",
  41. "DATETIME_INPUT_FORMATS",
  42. ]
  43. )
  44. def reset_format_cache():
  45. """Clear any cached formats.
  46. This method is provided primarily for testing purposes,
  47. so that the effects of cached formats can be removed.
  48. """
  49. global _format_cache, _format_modules_cache
  50. _format_cache = {}
  51. _format_modules_cache = {}
  52. def iter_format_modules(lang, format_module_path=None):
  53. """Find format modules."""
  54. if not check_for_language(lang):
  55. return
  56. if format_module_path is None:
  57. format_module_path = settings.FORMAT_MODULE_PATH
  58. format_locations = []
  59. if format_module_path:
  60. if isinstance(format_module_path, str):
  61. format_module_path = [format_module_path]
  62. for path in format_module_path:
  63. format_locations.append(path + ".%s")
  64. format_locations.append("django.conf.locale.%s")
  65. locale = to_locale(lang)
  66. locales = [locale]
  67. if "_" in locale:
  68. locales.append(locale.split("_")[0])
  69. for location in format_locations:
  70. for loc in locales:
  71. try:
  72. yield import_module("%s.formats" % (location % loc))
  73. except ImportError:
  74. pass
  75. def get_format_modules(lang=None):
  76. """Return a list of the format modules found."""
  77. if lang is None:
  78. lang = get_language()
  79. if lang not in _format_modules_cache:
  80. _format_modules_cache[lang] = list(
  81. iter_format_modules(lang, settings.FORMAT_MODULE_PATH)
  82. )
  83. return _format_modules_cache[lang]
  84. def get_format(format_type, lang=None, use_l10n=None):
  85. """
  86. For a specific format type, return the format for the current
  87. language (locale). Default to the format in the settings.
  88. format_type is the name of the format, e.g. 'DATE_FORMAT'.
  89. If use_l10n is provided and is not None, it forces the value to
  90. be localized (or not), overriding the value of settings.USE_L10N.
  91. """
  92. if use_l10n is None:
  93. try:
  94. use_l10n = settings._USE_L10N_INTERNAL
  95. except AttributeError:
  96. use_l10n = settings.USE_L10N
  97. if use_l10n and lang is None:
  98. lang = get_language()
  99. format_type = str(format_type) # format_type may be lazy.
  100. cache_key = (format_type, lang)
  101. try:
  102. return _format_cache[cache_key]
  103. except KeyError:
  104. pass
  105. # The requested format_type has not been cached yet. Try to find it in any
  106. # of the format_modules for the given lang if l10n is enabled. If it's not
  107. # there or if l10n is disabled, fall back to the project settings.
  108. val = None
  109. if use_l10n:
  110. for module in get_format_modules(lang):
  111. val = getattr(module, format_type, None)
  112. if val is not None:
  113. break
  114. if val is None:
  115. if format_type not in FORMAT_SETTINGS:
  116. return format_type
  117. val = getattr(settings, format_type)
  118. elif format_type in ISO_INPUT_FORMATS:
  119. # If a list of input formats from one of the format_modules was
  120. # retrieved, make sure the ISO_INPUT_FORMATS are in this list.
  121. val = list(val)
  122. for iso_input in ISO_INPUT_FORMATS.get(format_type, ()):
  123. if iso_input not in val:
  124. val.append(iso_input)
  125. _format_cache[cache_key] = val
  126. return val
  127. get_format_lazy = lazy(get_format, str, list, tuple)
  128. def date_format(value, format=None, use_l10n=None):
  129. """
  130. Format a datetime.date or datetime.datetime object using a
  131. localizable format.
  132. If use_l10n is provided and is not None, that will force the value to
  133. be localized (or not), overriding the value of settings.USE_L10N.
  134. """
  135. return dateformat.format(
  136. value, get_format(format or "DATE_FORMAT", use_l10n=use_l10n)
  137. )
  138. def time_format(value, format=None, use_l10n=None):
  139. """
  140. Format a datetime.time object using a localizable format.
  141. If use_l10n is provided and is not None, it forces the value to
  142. be localized (or not), overriding the value of settings.USE_L10N.
  143. """
  144. return dateformat.time_format(
  145. value, get_format(format or "TIME_FORMAT", use_l10n=use_l10n)
  146. )
  147. def number_format(value, decimal_pos=None, use_l10n=None, force_grouping=False):
  148. """
  149. Format a numeric value using localization settings.
  150. If use_l10n is provided and is not None, it forces the value to
  151. be localized (or not), overriding the value of settings.USE_L10N.
  152. """
  153. if use_l10n is None:
  154. try:
  155. use_l10n = settings._USE_L10N_INTERNAL
  156. except AttributeError:
  157. use_l10n = settings.USE_L10N
  158. lang = get_language() if use_l10n else None
  159. return numberformat.format(
  160. value,
  161. get_format("DECIMAL_SEPARATOR", lang, use_l10n=use_l10n),
  162. decimal_pos,
  163. get_format("NUMBER_GROUPING", lang, use_l10n=use_l10n),
  164. get_format("THOUSAND_SEPARATOR", lang, use_l10n=use_l10n),
  165. force_grouping=force_grouping,
  166. use_l10n=use_l10n,
  167. )
  168. def localize(value, use_l10n=None):
  169. """
  170. Check if value is a localizable type (date, number...) and return it
  171. formatted as a string using current locale format.
  172. If use_l10n is provided and is not None, it forces the value to
  173. be localized (or not), overriding the value of settings.USE_L10N.
  174. """
  175. if isinstance(value, str): # Handle strings first for performance reasons.
  176. return value
  177. elif isinstance(value, bool): # Make sure booleans don't get treated as numbers
  178. return str(value)
  179. elif isinstance(value, (decimal.Decimal, float, int)):
  180. if use_l10n is False:
  181. return str(value)
  182. return number_format(value, use_l10n=use_l10n)
  183. elif isinstance(value, datetime.datetime):
  184. return date_format(value, "DATETIME_FORMAT", use_l10n=use_l10n)
  185. elif isinstance(value, datetime.date):
  186. return date_format(value, use_l10n=use_l10n)
  187. elif isinstance(value, datetime.time):
  188. return time_format(value, "TIME_FORMAT", use_l10n=use_l10n)
  189. return value
  190. def localize_input(value, default=None):
  191. """
  192. Check if an input value is a localizable type and return it
  193. formatted with the appropriate formatting string of the current locale.
  194. """
  195. if isinstance(value, str): # Handle strings first for performance reasons.
  196. return value
  197. elif isinstance(value, bool): # Don't treat booleans as numbers.
  198. return str(value)
  199. elif isinstance(value, (decimal.Decimal, float, int)):
  200. return number_format(value)
  201. elif isinstance(value, datetime.datetime):
  202. format = default or get_format("DATETIME_INPUT_FORMATS")[0]
  203. format = sanitize_strftime_format(format)
  204. return value.strftime(format)
  205. elif isinstance(value, datetime.date):
  206. format = default or get_format("DATE_INPUT_FORMATS")[0]
  207. format = sanitize_strftime_format(format)
  208. return value.strftime(format)
  209. elif isinstance(value, datetime.time):
  210. format = default or get_format("TIME_INPUT_FORMATS")[0]
  211. return value.strftime(format)
  212. return value
  213. @functools.lru_cache
  214. def sanitize_strftime_format(fmt):
  215. """
  216. Ensure that certain specifiers are correctly padded with leading zeros.
  217. For years < 1000 specifiers %C, %F, %G, and %Y don't work as expected for
  218. strftime provided by glibc on Linux as they don't pad the year or century
  219. with leading zeros. Support for specifying the padding explicitly is
  220. available, however, which can be used to fix this issue.
  221. FreeBSD, macOS, and Windows do not support explicitly specifying the
  222. padding, but return four digit years (with leading zeros) as expected.
  223. This function checks whether the %Y produces a correctly padded string and,
  224. if not, makes the following substitutions:
  225. - %C → %02C
  226. - %F → %010F
  227. - %G → %04G
  228. - %Y → %04Y
  229. See https://bugs.python.org/issue13305 for more details.
  230. """
  231. if datetime.date(1, 1, 1).strftime("%Y") == "0001":
  232. return fmt
  233. mapping = {"C": 2, "F": 10, "G": 4, "Y": 4}
  234. return re.sub(
  235. r"((?:^|[^%])(?:%%)*)%([CFGY])",
  236. lambda m: r"%s%%0%s%s" % (m[1], mapping[m[2]], m[2]),
  237. fmt,
  238. )
  239. def sanitize_separators(value):
  240. """
  241. Sanitize a value according to the current decimal and
  242. thousand separator setting. Used with form field input.
  243. """
  244. if isinstance(value, str):
  245. parts = []
  246. decimal_separator = get_format("DECIMAL_SEPARATOR")
  247. if decimal_separator in value:
  248. value, decimals = value.split(decimal_separator, 1)
  249. parts.append(decimals)
  250. if settings.USE_THOUSAND_SEPARATOR:
  251. thousand_sep = get_format("THOUSAND_SEPARATOR")
  252. if (
  253. thousand_sep == "."
  254. and value.count(".") == 1
  255. and len(value.split(".")[-1]) != 3
  256. ):
  257. # Special case where we suspect a dot meant decimal separator
  258. # (see #22171).
  259. pass
  260. else:
  261. for replacement in {
  262. thousand_sep,
  263. unicodedata.normalize("NFKD", thousand_sep),
  264. }:
  265. value = value.replace(replacement, "")
  266. parts.append(value)
  267. value = ".".join(reversed(parts))
  268. return value