From f181adbe2649f7d4a1e0b24eb53bb090363ddc64 Mon Sep 17 00:00:00 2001 From: Vijay Misal Date: Mon, 10 Aug 2026 20:48:55 +0530 Subject: [PATCH] Don't crash on blank PO-Revision-Date/POT-Creation-Date headers Some tools (e.g. Poedit) leave PO-Revision-Date or POT-Creation-Date blank instead of using the YEAR-MO-DA placeholder or omitting the header. Catalog._set_mime_headers() passed the empty string straight to _parse_datetime_header(), which calls datetime.strptime('', '%Y-%m-%d %H:%M') and raises ValueError, crashing pybabel extract/update/compile. Wrap both call sites in contextlib.suppress(ValueError) so a blank or otherwise malformed date header is ignored (keeping the previous value) instead of aborting the whole command. Fixes #1219 --- babel/messages/catalog.py | 13 +++++++++++-- tests/messages/test_catalog.py | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index e01fd5677..26f9716fd 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -13,6 +13,7 @@ import datetime import re from collections.abc import Iterable, Iterator +from contextlib import suppress from copy import copy from difflib import SequenceMatcher from email import message_from_string @@ -582,11 +583,19 @@ def _set_mime_headers(self, headers: Iterable[tuple[str, str]]) -> None: self._num_plurals = int(params.get('nplurals', 2)) self._plural_expr = params.get('plural', '(n != 1)') elif name == 'pot-creation-date': - self.creation_date = _parse_datetime_header(value) + # Some tools (e.g. Poedit) may leave this header blank or + # otherwise malformed; rather than crashing, just ignore it + # and keep the existing value in that case. + with suppress(ValueError): + self.creation_date = _parse_datetime_header(value) elif name == 'po-revision-date': # Keep the value if it's not the default one if 'YEAR' not in value: - self.revision_date = _parse_datetime_header(value) + # Some tools (e.g. Poedit) may leave this header blank or + # otherwise malformed; rather than crashing, just ignore + # it and keep the existing value in that case. + with suppress(ValueError): + self.revision_date = _parse_datetime_header(value) @property def mime_headers(self) -> list[tuple[str, str]]: diff --git a/tests/messages/test_catalog.py b/tests/messages/test_catalog.py index 4a60208c8..9ca512b4e 100644 --- a/tests/messages/test_catalog.py +++ b/tests/messages/test_catalog.py @@ -335,6 +335,22 @@ def test_catalog_update_po_keeps_po_revision_date(): assert localized_catalog.revision_date == fake_rev_date +def test_catalog_set_mime_headers_ignores_blank_dates(): + # Some tools (e.g. Poedit) can leave the PO-Revision-Date and/or + # POT-Creation-Date headers blank instead of omitting them or using the + # "YEAR-MO-DA HO:MI+ZONE" placeholder. This used to raise a ValueError + # instead of being handled gracefully. + cat = catalog.Catalog() + original_creation_date = cat.creation_date + original_revision_date = cat.revision_date + cat._set_mime_headers([ + ('POT-Creation-Date', ''), + ('PO-Revision-Date', ''), + ]) + assert cat.creation_date == original_creation_date + assert cat.revision_date == original_revision_date + + def test_catalog_stores_datetime_correctly(): localized = catalog.Catalog() localized.locale = 'de_DE'