Repository: wagtail/wagtail

Issue:
Add system checks for `WAGTAIL_DATE_FORMAT`, `WAGTAIL_DATETIME_FORMAT`, `WAGTAIL_TIME_FORMAT` being listed in Django configured formats
### Is your proposal related to a problem?

When configuring any of the `WAGTAIL_*_FORMAT` configs, these must be included in the relevant Django formats.

https://docs.wagtail.org/en/stable/reference/settings.html#wagtail-date-format-wagtail-datetime-format-wagtail-time-format

> ...must be one of the recognised formats listed in the DATE_INPUT_FORMATS, TIME_INPUT_FORMATS, or DATETIME_INPUT_FORMATS setting respectively 

This config coordination is easy to to get wrong causing non-intuitive behaviour as Wagtail/Django will fallback to defaults.

### Describe the solution you'd like

* Using the [Django system check framework](https://docs.djangoproject.com/en/5.0/topics/checks/) or similar, ensure that if there are misconfiguration issues there will be an error thrown or report at app startup.

### Describe alternatives you've considered

* Leave as is, the documentation advises what to do.

### Additional context

* An older PR had a really solid start to this but had a few questions and was only a draft. https://github.com/wagtail/wagtail/pull/6168
* It may make sense to start with a simple version of these checks as a smaller scoped PR.

### Working on this

* This would be good for anyone willing to dig deep into these configs & their Django equivalents.
* Some knowledge or learning of the way the Django system check framework works and exploration of the current usage in Wagtail.
* The PR linked above would be a good start but may not be the right way forward, it also did not have tests and tests are a must.
* View our [contributing guidelines](https://docs.wagtail.org/en/latest/contributing/index.html), add a comment to the issue once you’re ready to start.



Discussion:
@riddhisharma-sudo Sorry to be blunt, but please refrain from using AI to write comments that do not add any meaningful value to the issue. Your comment just rephrases the issue description.
@rohitsrma happy to help with any specific questions you have about how to go about implementing this. 

Here's two PRs that added system checks that may help guide you or anyone else. 

- https://github.com/wagtail/wagtail/pull/8258
- https://github.com/wagtail/wagtail/pull/10873

Thank you @lb-,  I'm eager to give this a try!

Resolving patch:
diff --git a/wagtail/admin/checks.py b/wagtail/admin/checks.py
--- a/wagtail/admin/checks.py
+++ b/wagtail/admin/checks.py
@@ -235,3 +235,54 @@ def file_overwrite_check(app_configs, **kwargs):
         )
 
     return errors
+
+
+@register("datetime_format")
+def datetime_format_check(app_configs, **kwargs):
+    """
+    If L10N is enabled, check if WAGTAIL_* formats are compatible with Django input formats.
+    See https://docs.djangoproject.com/en/stable/topics/i18n/formatting/#creating-custom-format-files
+    See https://docs.wagtail.org/en/stable/reference/settings.html#wagtail-date-format-wagtail-datetime-format-wagtail-time-format
+    """
+
+    from django.conf import settings
+    from django.utils import formats, translation
+
+    errors = []
+
+    if not getattr(settings, "USE_L10N", False):
+        return errors
+
+    formats.FORMAT_SETTINGS = formats.FORMAT_SETTINGS.union(
+        [
+            "WAGTAIL_DATE_FORMAT",
+            "WAGTAIL_DATETIME_FORMAT",
+            "WAGTAIL_TIME_FORMAT",
+        ]
+    )
+
+    for code, label in settings.LANGUAGES:
+        with translation.override(code):
+            for wagtail_format, django_formats in [
+                ("WAGTAIL_DATE_FORMAT", "DATE_INPUT_FORMATS"),
+                ("WAGTAIL_DATETIME_FORMAT", "DATETIME_INPUT_FORMATS"),
+                ("WAGTAIL_TIME_FORMAT", "TIME_INPUT_FORMATS"),
+            ]:
+                wagtail_format_value = getattr(settings, wagtail_format, None)
+                django_formats_value = getattr(settings, django_formats, None)
+
+                if wagtail_format_value is None:
+                    # Skip the iteration if wagtail_format is not present
+                    continue
+
+                input_format = formats.get_format_lazy(wagtail_format_value)
+                input_formats = formats.get_format_lazy(django_formats_value)
+                if str(input_format) not in str(input_formats):
+                    errors.append(
+                        Error(
+                            "Configuration error",
+                            hint=f"{wagtail_format} {input_format} must be in {django_formats} for language {label} ({code}).",
+                        )
+                    )
+
+    return errors


Test patch:
diff --git a/wagtail/admin/tests/test_checks.py b/wagtail/admin/tests/test_checks.py
new file mode 100644
--- /dev/null
+++ b/wagtail/admin/tests/test_checks.py
@@ -0,0 +1,92 @@
+from django.core.checks import Error
+from django.test import TestCase, override_settings
+
+from wagtail.admin.checks import datetime_format_check
+from wagtail.test.utils import WagtailTestUtils
+
+
+class TestDateTimeChecks(WagtailTestUtils, TestCase):
+    fixtures = ["test.json"]
+
+    def test_datetime_format(self):
+        with override_settings(
+            WAGTAIL_CONTENT_LANGUAGES=[
+                ("en", "English"),
+            ],
+            LANGUAGES=[
+                ("en", "English"),
+            ],
+            WAGTAIL_DATE_FORMAT="%m/%d/%Y",
+            WAGTAIL_TIME_FORMAT="%H:%M",
+            USE_L10N=True,
+        ):
+            errors = datetime_format_check(None)
+
+        self.assertEqual(errors, [])
+
+    def test_datetime_format_with_unsupported_date(self):
+        with override_settings(
+            WAGTAIL_CONTENT_LANGUAGES=[
+                ("en", "English"),
+            ],
+            LANGUAGES=[
+                ("en", "English"),
+            ],
+            WAGTAIL_DATE_FORMAT="%d.%m.%Y.",
+            WAGTAIL_TIME_FORMAT="%H:%M",
+            USE_L10N=True,
+        ):
+            errors = datetime_format_check(None)
+
+        expected_errors = [
+            Error(
+                "Configuration error",
+                hint="WAGTAIL_DATE_FORMAT %d.%m.%Y. must be in DATE_INPUT_FORMATS for language English (en).",
+            )
+        ]
+        self.assertEqual(errors, expected_errors)
+
+    def test_datetime_format_with_unsupported_date_not_using_l10n(self):
+        """
+        Test that the check doesn't raise an error when USE_L10N is False.
+        """
+
+        with override_settings(
+            WAGTAIL_CONTENT_LANGUAGES=[
+                ("en", "English"),
+            ],
+            LANGUAGES=[
+                ("en", "English"),
+            ],
+            WAGTAIL_DATE_FORMAT="%d.%m.%Y.",
+            WAGTAIL_TIME_FORMAT="%H:%M",
+            USE_L10N=False,
+        ):
+            errors = datetime_format_check(None)
+        self.assertEqual(errors, [])
+
+    def test_datetime_format_with_unsupported_datetime_and_time(self):
+        with override_settings(
+            WAGTAIL_CONTENT_LANGUAGES=[
+                ("en", "English"),
+            ],
+            LANGUAGES=[
+                ("en", "English"),
+            ],
+            WAGTAIL_DATETIME_FORMAT="%d.%m.%Y. %H:%M",
+            WAGTAIL_TIME_FORMAT="%I:%M %p",
+            USE_L10N=True,
+        ):
+            errors = datetime_format_check(None)
+
+        expected_errors = [
+            Error(
+                "Configuration error",
+                hint="WAGTAIL_DATETIME_FORMAT %d.%m.%Y. %H:%M must be in DATETIME_INPUT_FORMATS for language English (en).",
+            ),
+            Error(
+                "Configuration error",
+                hint="WAGTAIL_TIME_FORMAT %I:%M %p must be in TIME_INPUT_FORMATS for language English (en).",
+            ),
+        ]
+        self.assertEqual(errors, expected_errors)