Refs #32738 -- Added sanitize_strftime_format() to replace datetime_safe.
This commit is contained in:
parent
44accb066a
commit
46346f8ea0
|
@ -10,7 +10,7 @@ from itertools import chain
|
||||||
|
|
||||||
from django.forms.utils import to_current_timezone
|
from django.forms.utils import to_current_timezone
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.utils import datetime_safe, formats
|
from django.utils import formats
|
||||||
from django.utils.datastructures import OrderedSet
|
from django.utils.datastructures import OrderedSet
|
||||||
from django.utils.dates import MONTHS
|
from django.utils.dates import MONTHS
|
||||||
from django.utils.formats import get_format
|
from django.utils.formats import get_format
|
||||||
|
@ -1070,13 +1070,13 @@ class SelectDateWidget(Widget):
|
||||||
return None
|
return None
|
||||||
if y is not None and m is not None and d is not None:
|
if y is not None and m is not None and d is not None:
|
||||||
input_format = get_format('DATE_INPUT_FORMATS')[0]
|
input_format = get_format('DATE_INPUT_FORMATS')[0]
|
||||||
|
input_format = formats.sanitize_strftime_format(input_format)
|
||||||
try:
|
try:
|
||||||
date_value = datetime.date(int(y), int(m), int(d))
|
date_value = datetime.date(int(y), int(m), int(d))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Return pseudo-ISO dates with zeros for any unselected values,
|
# Return pseudo-ISO dates with zeros for any unselected values,
|
||||||
# e.g. '2017-0-23'.
|
# e.g. '2017-0-23'.
|
||||||
return '%s-%s-%s' % (y or 0, m or 0, d or 0)
|
return '%s-%s-%s' % (y or 0, m or 0, d or 0)
|
||||||
date_value = datetime_safe.new_date(date_value)
|
|
||||||
return date_value.strftime(input_format)
|
return date_value.strftime(input_format)
|
||||||
return data.get(name)
|
return data.get(name)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
|
import functools
|
||||||
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import dateformat, datetime_safe, numberformat
|
from django.utils import dateformat, numberformat
|
||||||
from django.utils.functional import lazy
|
from django.utils.functional import lazy
|
||||||
from django.utils.translation import (
|
from django.utils.translation import (
|
||||||
check_for_language, get_language, to_locale,
|
check_for_language, get_language, to_locale,
|
||||||
|
@ -221,12 +223,12 @@ def localize_input(value, default=None):
|
||||||
elif isinstance(value, (decimal.Decimal, float, int)):
|
elif isinstance(value, (decimal.Decimal, float, int)):
|
||||||
return number_format(value)
|
return number_format(value)
|
||||||
elif isinstance(value, datetime.datetime):
|
elif isinstance(value, datetime.datetime):
|
||||||
value = datetime_safe.new_datetime(value)
|
|
||||||
format = default or get_format('DATETIME_INPUT_FORMATS')[0]
|
format = default or get_format('DATETIME_INPUT_FORMATS')[0]
|
||||||
|
format = sanitize_strftime_format(format)
|
||||||
return value.strftime(format)
|
return value.strftime(format)
|
||||||
elif isinstance(value, datetime.date):
|
elif isinstance(value, datetime.date):
|
||||||
value = datetime_safe.new_date(value)
|
|
||||||
format = default or get_format('DATE_INPUT_FORMATS')[0]
|
format = default or get_format('DATE_INPUT_FORMATS')[0]
|
||||||
|
format = sanitize_strftime_format(format)
|
||||||
return value.strftime(format)
|
return value.strftime(format)
|
||||||
elif isinstance(value, datetime.time):
|
elif isinstance(value, datetime.time):
|
||||||
format = default or get_format('TIME_INPUT_FORMATS')[0]
|
format = default or get_format('TIME_INPUT_FORMATS')[0]
|
||||||
|
@ -234,6 +236,39 @@ def localize_input(value, default=None):
|
||||||
return value
|
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):
|
def sanitize_separators(value):
|
||||||
"""
|
"""
|
||||||
Sanitize a value according to the current decimal and
|
Sanitize a value according to the current decimal and
|
||||||
|
|
|
@ -477,7 +477,8 @@ class SelectDateWidgetTest(WidgetTest):
|
||||||
w.value_from_datadict({'date_year': '1899', 'date_month': '8', 'date_day': '13'}, {}, 'date'),
|
w.value_from_datadict({'date_year': '1899', 'date_month': '8', 'date_day': '13'}, {}, 'date'),
|
||||||
'13-08-1899',
|
'13-08-1899',
|
||||||
)
|
)
|
||||||
# And years before 1000 (demonstrating the need for datetime_safe).
|
# And years before 1000 (demonstrating the need for
|
||||||
|
# sanitize_strftime_format).
|
||||||
w = SelectDateWidget(years=('0001',))
|
w = SelectDateWidget(years=('0001',))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
w.value_from_datadict({'date_year': '0001', 'date_month': '8', 'date_day': '13'}, {}, 'date'),
|
w.value_from_datadict({'date_year': '0001', 'date_month': '8', 'date_day': '13'}, {}, 'date'),
|
||||||
|
|
|
@ -24,7 +24,8 @@ from django.test import (
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.formats import (
|
from django.utils.formats import (
|
||||||
date_format, get_format, get_format_modules, iter_format_modules, localize,
|
date_format, get_format, get_format_modules, iter_format_modules, localize,
|
||||||
localize_input, reset_format_cache, sanitize_separators, time_format,
|
localize_input, reset_format_cache, sanitize_separators,
|
||||||
|
sanitize_strftime_format, time_format,
|
||||||
)
|
)
|
||||||
from django.utils.numberformat import format as nformat
|
from django.utils.numberformat import format as nformat
|
||||||
from django.utils.safestring import SafeString, mark_safe
|
from django.utils.safestring import SafeString, mark_safe
|
||||||
|
@ -1074,6 +1075,51 @@ class FormattingTests(SimpleTestCase):
|
||||||
with self.subTest(value=value):
|
with self.subTest(value=value):
|
||||||
self.assertEqual(localize_input(value), expected)
|
self.assertEqual(localize_input(value), expected)
|
||||||
|
|
||||||
|
def test_sanitize_strftime_format(self):
|
||||||
|
for year in (1, 99, 999, 1000):
|
||||||
|
dt = datetime.date(year, 1, 1)
|
||||||
|
for fmt, expected in [
|
||||||
|
('%C', '%02d' % (year // 100)),
|
||||||
|
('%F', '%04d-01-01' % year),
|
||||||
|
('%G', '%04d' % year),
|
||||||
|
('%Y', '%04d' % year),
|
||||||
|
]:
|
||||||
|
with self.subTest(year=year, fmt=fmt):
|
||||||
|
fmt = sanitize_strftime_format(fmt)
|
||||||
|
self.assertEqual(dt.strftime(fmt), expected)
|
||||||
|
|
||||||
|
def test_sanitize_strftime_format_with_escaped_percent(self):
|
||||||
|
dt = datetime.date(1, 1, 1)
|
||||||
|
for fmt, expected in [
|
||||||
|
('%%C', '%C'),
|
||||||
|
('%%F', '%F'),
|
||||||
|
('%%G', '%G'),
|
||||||
|
('%%Y', '%Y'),
|
||||||
|
('%%%%C', '%%C'),
|
||||||
|
('%%%%F', '%%F'),
|
||||||
|
('%%%%G', '%%G'),
|
||||||
|
('%%%%Y', '%%Y'),
|
||||||
|
]:
|
||||||
|
with self.subTest(fmt=fmt):
|
||||||
|
fmt = sanitize_strftime_format(fmt)
|
||||||
|
self.assertEqual(dt.strftime(fmt), expected)
|
||||||
|
|
||||||
|
for year in (1, 99, 999, 1000):
|
||||||
|
dt = datetime.date(year, 1, 1)
|
||||||
|
for fmt, expected in [
|
||||||
|
('%%%C', '%%%02d' % (year // 100)),
|
||||||
|
('%%%F', '%%%04d-01-01' % year),
|
||||||
|
('%%%G', '%%%04d' % year),
|
||||||
|
('%%%Y', '%%%04d' % year),
|
||||||
|
('%%%%%C', '%%%%%02d' % (year // 100)),
|
||||||
|
('%%%%%F', '%%%%%04d-01-01' % year),
|
||||||
|
('%%%%%G', '%%%%%04d' % year),
|
||||||
|
('%%%%%Y', '%%%%%04d' % year),
|
||||||
|
]:
|
||||||
|
with self.subTest(year=year, fmt=fmt):
|
||||||
|
fmt = sanitize_strftime_format(fmt)
|
||||||
|
self.assertEqual(dt.strftime(fmt), expected)
|
||||||
|
|
||||||
def test_sanitize_separators(self):
|
def test_sanitize_separators(self):
|
||||||
"""
|
"""
|
||||||
Tests django.utils.formats.sanitize_separators.
|
Tests django.utils.formats.sanitize_separators.
|
||||||
|
|
Loading…
Reference in New Issue