Repository: wtforms/wtforms
Pull request #703 (2021-08-03 18:44:19)

add compatibility for removing leading-zeros in strftime/strptime
The datetime format specifier for `datetime.strftime` allows to remove leading zeros from outputted data. However, attempting to use the same format string with `datetime.strptime` raises with `ValueError: '-' is a bad directive in format '%-m/%-d/%Y %-I:%M %p %z'`. This PR fixes the issue.

<!--
Commit checklist:

* add tests that fail without the patch
* ensure all tests pass with ``pytest``
* add documentation to the relevant docstrings or pages
* add ``versionadded`` or ``versionchanged`` directives to relevant docstrings
* add a changelog entry if this patch changes code
-->



Patch:
diff --git a/src/wtforms/fields/core.py b/src/wtforms/fields/core.py
index 96484a9..7ea8e20 100644
--- a/src/wtforms/fields/core.py
+++ b/src/wtforms/fields/core.py
@@ -9,7 +9,7 @@ from markupsafe import Markup
 
 from wtforms import widgets
 from wtforms.i18n import DummyTranslations
-from wtforms.utils import unset_value
+from wtforms.utils import clean_datetime_format_for_strptime, unset_value
 from wtforms.validators import StopValidation
 from wtforms.validators import ValidationError
 
@@ -893,6 +893,7 @@ class DateTimeField(Field):
     ):
         super().__init__(label, validators, **kwargs)
         self.format = format
+        self.strptime_format = clean_datetime_format_for_strptime(format)
 
     def _value(self):
         if self.raw_data:
@@ -905,7 +906,7 @@ class DateTimeField(Field):
 
         date_str = " ".join(valuelist)
         try:
-            self.data = datetime.datetime.strptime(date_str, self.format)
+            self.data = datetime.datetime.strptime(date_str, self.strptime_format)
         except ValueError:
             self.data = None
             raise ValueError(self.gettext("Not a valid datetime value."))
@@ -927,7 +928,7 @@ class DateField(DateTimeField):
 
         date_str = " ".join(valuelist)
         try:
-            self.data = datetime.datetime.strptime(date_str, self.format).date()
+            self.data = datetime.datetime.strptime(date_str, self.strptime_format).date()
         except ValueError:
             self.data = None
             raise ValueError(self.gettext("Not a valid date value."))
@@ -949,7 +950,7 @@ class TimeField(DateTimeField):
 
         time_str = " ".join(valuelist)
         try:
-            self.data = datetime.datetime.strptime(time_str, self.format).time()
+            self.data = datetime.datetime.strptime(time_str, self.strptime_format).time()
         except ValueError:
             self.data = None
             raise ValueError(self.gettext("Not a valid time value."))
diff --git a/src/wtforms/utils.py b/src/wtforms/utils.py
index aa67886..1fac614 100644
--- a/src/wtforms/utils.py
+++ b/src/wtforms/utils.py
@@ -1,3 +1,33 @@
+import re
+
+
+# https://docs.python.org/3/library/datetime.html#technical-detail (see NOTE #9)
+_DATETIME_STRIP_ZERO_PADDING_FORMATS_RE = re.compile(
+    '%-['
+    'd'  # day of month
+    'm'  # month
+    'H'  # hour (24-hour)
+    'I'  # hour (12-hour)
+    'M'  # minutes
+    'S'  # seconds
+    'U'  # week of year (Sunday first day of week)
+    'W'  # week of year (Monday first day of week)
+    'V'  # week of year (ISO 8601)
+    ']',
+    re.MULTILINE,
+)
+
+
+def clean_datetime_format_for_strptime(format):
+    """
+    Remove dashes used to disable zero-padding with strftime formats (for
+    compatibiltity with strptime).
+    """
+    return re.sub(_DATETIME_STRIP_ZERO_PADDING_FORMATS_RE,
+                  lambda m: m[0].replace('-', ''),
+                  format)
+
+
 class UnsetValue:
     """
     An unset value.


Test patch:
diff --git a/tests/fields/test_date.py b/tests/fields/test_date.py
index 873a6a0..b1c7b93 100644
--- a/tests/fields/test_date.py
+++ b/tests/fields/test_date.py
@@ -9,15 +9,18 @@ from wtforms.form import Form
 class F(Form):
     a = DateField()
     b = DateField(format="%m/%d %Y")
+    c = DateField(format="%-m/%-d %Y")
 
 
 def test_basic():
     d = date(2008, 5, 7)
-    form = F(DummyPostData(a=["2008-05-07"], b=["05/07", "2008"]))
+    form = F(DummyPostData(a=["2008-05-07"], b=["05/07", "2008"], c=["5/7 2008"]))
     assert form.a.data == d
     assert form.a._value() == "2008-05-07"
     assert form.b.data == d
     assert form.b._value() == "05/07 2008"
+    assert form.c.data == d
+    assert form.c._value() == "5/7 2008"
 
 
 def test_failure():
diff --git a/tests/fields/test_datetime.py b/tests/fields/test_datetime.py
index 008799f..b0f88f2 100644
--- a/tests/fields/test_datetime.py
+++ b/tests/fields/test_datetime.py
@@ -13,12 +13,15 @@ def make_form(name="F", **fields):
 class F(Form):
     a = DateTimeField()
     b = DateTimeField(format="%Y-%m-%d %H:%M")
+    c = DateTimeField(format="%-m/%-d/%Y %-I:%M")
 
 
 def test_basic():
     d = datetime(2008, 5, 5, 4, 30, 0, 0)
     # Basic test with both inputs
-    form = F(DummyPostData(a=["2008-05-05", "04:30:00"], b=["2008-05-05 04:30"]))
+    form = F(DummyPostData(a=["2008-05-05", "04:30:00"],
+                           b=["2008-05-05 04:30"],
+                           c=["5/5/2008 4:30"]))
     assert form.a.data == d
     assert (
         form.a()
@@ -29,6 +32,11 @@ def test_basic():
         form.b()
         == """<input id="b" name="b" type="datetime" value="2008-05-05 04:30">"""
     )
+    assert form.c.data == d
+    assert (
+        form.c()
+        == """<input id="c" name="c" type="datetime" value="5/5/2008 4:30">"""
+    )
     assert form.validate()
 
     # Test with a missing input
@@ -36,9 +44,11 @@ def test_basic():
     assert not form.validate()
     assert form.a.errors[0] == "Not a valid datetime value."
 
-    form = F(a=d, b=d)
+    form = F(a=d, b=d, c=d)
     assert form.validate()
     assert form.a._value() == "2008-05-05 04:30:00"
+    assert form.b._value() == "2008-05-05 04:30"
+    assert form.c._value() == "5/5/2008 4:30"
 
 
 def test_microseconds():