Repository: wtforms/wtforms (python)

Issue:
Support fields for week, month and color
Please, support fields for week, month and color. See the example at https://getbootstrap.com/docs/5.1/content/reboot/?optionsRadios=option1#forms

        <p>
          <label for="week">Example week</label>
          <input type="week" id="week">
        </p>
        <p>
          <label for="month">Example month</label>
          <input type="month" id="month">
        </p>
        <p>
          <label for="color">Example color</label>
          <input type="color" id="color">
        </p>

Pull request description:
Implemented WeekField
This implements the [HTML week input field](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/week). The default expected format is `%Y-W%W` according to the [mozilla doc](https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#week_strings):

> A week string specifies a week within a particular year. A valid week string consists of a valid [year number](https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#year_numbers), followed by a hyphen character ("-", or U+002D), then the capital letter "W" (U+0057), followed by a two-digit week of the year value.

I am not very happy with the manual adding of `%w` but I did not find any other way to achieve this.

This should close #725

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 9f2cd01..6ff5168 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -16,6 +16,8 @@ Unreleased
 -   Added shorter format to :class:`~fields.DateTimeLocalField`
     defaults :pr:`761`
 -   Stop support for python 3.7 :pr:`794`
+-   Added shorter format to :class:`~fields.WeekField`
+    defaults :pr:`765`
 
 Version 3.0.1
 -------------
diff --git a/src/wtforms/fields/datetime.py b/src/wtforms/fields/datetime.py
index c4bf2a5..63e32d7 100644
--- a/src/wtforms/fields/datetime.py
+++ b/src/wtforms/fields/datetime.py
@@ -10,6 +10,7 @@ __all__ = (
     "TimeField",
     "MonthField",
     "DateTimeLocalField",
+    "WeekField",
 )
 
 
@@ -114,6 +115,39 @@ class MonthField(DateField):
         super().__init__(label, validators, format, **kwargs)
 
 
+class WeekField(DateField):
+    """
+    Same as :class:`~wtforms.fields.DateField`, except represents a week,
+    stores a :class:`datetime.date` of the monday of the given week.
+    """
+
+    widget = widgets.WeekInput()
+
+    def __init__(self, label=None, validators=None, format="%Y-W%W", **kwargs):
+        super().__init__(label, validators, format, **kwargs)
+
+    def process_formdata(self, valuelist):
+        if not valuelist:
+            return
+
+        time_str = " ".join(valuelist)
+        for format in self.strptime_format:
+            try:
+                if "%w" not in format:
+                    # The '%w' week starting day is needed. This defaults it to monday
+                    # like ISO 8601 indicates.
+                    self.data = datetime.datetime.strptime(
+                        f"{time_str}-1", f"{format}-%w"
+                    ).date()
+                else:
+                    self.data = datetime.datetime.strptime(time_str, format).date()
+                return
+            except ValueError:
+                self.data = None
+
+        raise ValueError(self.gettext("Not a valid week value."))
+
+
 class DateTimeLocalField(DateTimeField):
     """
     Same as :class:`~wtforms.fields.DateTimeField`, but represents an


Test patch:
diff --git a/tests/fields/test_week.py b/tests/fields/test_week.py
new file mode 100644
index 0000000..527263b
--- /dev/null
+++ b/tests/fields/test_week.py
@@ -0,0 +1,36 @@
+from datetime import date
+
+from tests.common import DummyPostData
+
+from wtforms.fields import WeekField
+from wtforms.form import Form
+
+
+class F(Form):
+    a = WeekField()
+    b = WeekField(format="W%W %Y")
+    c = WeekField(format="%Y-W%W-%w")
+
+
+def test_basic():
+    form = F(DummyPostData(a=["2023-W03"], b=["W03 2023"], c=["2023-W03-0"]))
+
+    assert form.a.data == date(2023, 1, 16)
+    assert "2023-W03" == form.a._value()
+    assert form.a() == '<input id="a" name="a" type="week" value="2023-W03">'
+
+    assert form.b.data == date(2023, 1, 16)
+    assert form.b() == '<input id="b" name="b" type="week" value="W03 2023">'
+
+    # %W makes the week count on the first monday of the year
+    assert form.c.data == date(2023, 1, 22)
+    assert form.c() == '<input id="c" name="c" type="week" value="2023-W03-0">'
+
+
+def test_invalid_data():
+    form = F(DummyPostData(a=["2008-Wbb"]))
+
+    assert not form.validate()
+    assert 1 == len(form.a.process_errors)
+    assert 1 == len(form.a.errors)
+    assert "Not a valid week value." == form.a.process_errors[0]