Browse Source

Fixed #8809 -- ExcelDateFormatter generates invalid format strings (#8811)

The format strings generated previously would not work correctly in macOs Preview and Numbers
Jaap Roes 2 years ago
parent
commit
12ba7d8161
4 changed files with 93 additions and 37 deletions
  1. 1 0
      CHANGELOG.txt
  2. 1 0
      docs/releases/4.0.md
  3. 11 0
      wagtail/admin/tests/test_reports_views.py
  4. 80 37
      wagtail/admin/views/mixins.py

+ 1 - 0
CHANGELOG.txt

@@ -78,6 +78,7 @@ Changelog
  * Fix: When no snippets are added, ensure the snippet chooser modal would have the correct URL for creating a new snippet (Matt Westcott)
  * Fix: `ngettext` in Wagtail's internal JavaScript internationalisation utilities now works (LB (Ben) Johnston)
  * Fix: Ensure the linting/formatting npm scripts work on Windows (Anuja Verma)
+ * Fix: Fix display of dates in exported xlsx files on macOS Preview and Numbers (Jaap Roes)
 
 
 3.0.1 (16.06.2022)

+ 1 - 0
docs/releases/4.0.md

@@ -90,6 +90,7 @@ When using a queryset to render a list of images, you can now use the `prefetch_
  * When no snippets are added, ensure the snippet chooser modal would have the correct URL for creating a new snippet (Matt Westcott)
  * `ngettext` in Wagtail's internal JavaScript internationalisation utilities now works (LB (Ben) Johnston)
  * Ensure the linting/formatting npm scripts work on Windows (Anuja Verma)
+ * Fix display of dates in exported xlsx files on macOS Preview and Numbers (Jaap Roes)
 
 
 ## Upgrade considerations

+ 11 - 0
wagtail/admin/tests/test_reports_views.py

@@ -262,6 +262,17 @@ class TestExcelDateFormatter(TestCase):
             with self.subTest(lang), translation.override(lang):
                 self.assertNotEqual(formatter.get(), "")
 
+    def test_format(self):
+        formatter = ExcelDateFormatter()
+
+        with self.subTest(format="r"):
+            # Format code for RFC 5322 formatted date, e.g. 'Thu, 21 Dec 2000 16:01:07'
+            self.assertEqual(formatter.format("r"), "ddd, d mmm yyyy hh:mm:ss")
+
+        with self.subTest(format="m/d/Y g:i A"):
+            # Format code for e.g. '12/21/2000 4:01 PM'
+            self.assertEqual(formatter.format("m/d/Y g:i A"), "mm/dd/yyyy h:mm AM/PM")
+
 
 class TestAgingPagesView(TestCase, WagtailTestUtils):
     def setUp(self):

+ 80 - 37
wagtail/admin/views/mixins.py

@@ -68,44 +68,87 @@ def list_to_str(value):
 class ExcelDateFormatter(Formatter):
     data = None
 
+    # From: https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
+    # To: https://support.microsoft.com/en-us/office/format-numbers-as-dates-or-times-418bd3fe-0577-47c8-8caa-b4d30c528309#bm2
     _formats = {
-        "d": "DD",
-        "j": "D",
-        "D": "NN",
-        "l": "NNNN",
-        "S": "",
-        "w": "",
-        "z": "",
-        "W": "",
-        "m": "MM",
-        "n": "M",
-        "M": "MMM",
-        "b": "MMM",
-        "F": "MMMM",
-        "E": "MMM",
-        "N": "MMM.",
-        "y": "YY",
-        "Y": "YYYY",
-        "L": "",
-        "o": "",
-        "g": "H",
-        "G": "H",
-        "h": "HH",
-        "H": "HH",
-        "i": "MM",
-        "s": "SS",
-        "u": "",
-        "a": "AM/PM",
-        "A": "AM/PM",
-        "P": "HH:MM AM/PM",
-        "e": "",
-        "I": "",
-        "O": "",
-        "T": "",
-        "Z": "",
-        "c": "YYYY-MM-DD HH:MM:SS",
-        "r": "NN, MMM D YY HH:MM:SS",
-        "U": "[HH]:MM:SS",
+        # Day of the month, 2 digits with leading zeros.
+        "d": "dd",
+        # Day of the month without leading zeros.
+        "j": "d",
+        # Day of the week, textual, 3 letters.
+        "D": "ddd",
+        # Day of the week, textual, full.
+        "l": "dddd",
+        # English ordinal suffix for the day of the month, 2 characters.
+        "S": "",  # Not supported in Excel
+        # Day of the week, digits without leading zeros.
+        "w": "",  # Not supported in Excel
+        # Day of the year.
+        "z": "",  # Not supported in Excel
+        # ISO-8601 week number of year, with weeks starting on Monday.
+        "W": "",  # Not supported in Excel
+        # Month, 2 digits with leading zeros.
+        "m": "mm",
+        # Month without leading zeros.
+        "n": "m",
+        # Month, textual, 3 letters.
+        "M": "mmm",
+        # Month, textual, 3 letters, lowercase. (Not supported in Excel)
+        "b": "mmm",
+        # Month, locale specific alternative representation usually used for long date representation.
+        "E": "mmmm",  # Not supported in Excel
+        # Month, textual, full.
+        "F": "mmmm",
+        # Month abbreviation in Associated Press style. Proprietary extension.
+        "N": "mmm.",  # Approximation, wrong for May
+        # Number of days in the given month.
+        "t": "",  # Not supported in Excel
+        # Year, 2 digits with leading zeros.
+        "y": "yy",
+        # Year, 4 digits with leading zeros.
+        "Y": "yyyy",
+        # Whether it's a leap year.
+        "L": "",  # Not supported in Excel
+        # ISO-8601 week-numbering year.
+        "o": "yyyy",  # Approximation, same as Y
+        # Hour, 12-hour format without leading zeros.
+        "g": "h",  # Only works when combined with AM/PM, 24-hour format is used otherwise
+        # Hour, 24-hour format without leading zeros.
+        "G": "hH",
+        # Hour, 12-hour format with leading zeros.
+        "h": "hh",  # Only works when combined with AM/PM, 24-hour format is used otherwise
+        # Hour, 24-hour format with leading zeros.
+        "H": "hh",
+        # Minutes.
+        "i": "mm",
+        # Seconds.
+        "s": "ss",
+        # Microseconds.
+        "u": ".00",  # Only works when combined with ss
+        # 'a.m.' or 'p.m.'.
+        "a": "AM/PM",  # Approximation, uses AM/PM and only works when combined with h/hh
+        # AM/PM.
+        "A": "AM/PM",  # Only works when combined with h/hh
+        # Time, in 12-hour hours and minutes, with minutes left off if they’re zero.
+        "f": "h:mm",  # Approximation, uses 24-hour format and minutes are never left off
+        # Time, in 12-hour hours, minutes and ‘a.m.’/’p.m.’, with minutes left off if they’re zero and the special-case strings ‘midnight’ and ‘noon’ if appropriate.
+        "P": "h:mm AM/PM",  # Approximation, minutes are never left off, no special case strings
+        # Timezone name.
+        "e": "",  # Not supported in Excel
+        # Daylight saving time, whether it’s in effect or not.
+        "I": "",  # Not supported in Excel
+        # Difference to Greenwich time in hours.
+        "O": "",  # Not supported in Excel
+        # Time zone of this machine.
+        "T": "",  # Not supported in Excel
+        # Timezone offset in seconds.
+        "Z": "",  # Not supported in Excel
+        # ISO 8601 format.
+        "c": "yyyy-mm-ddThh:mm:ss.00",
+        # RFC 5322 formatted date.
+        "r": "ddd, d mmm yyyy hh:mm:ss",
+        # Seconds since the Unix epoch.
+        "U": "",  # Not supported in Excel
     }
 
     def get(self):