diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index 5686b3141..adae85f47 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -103,10 +103,18 @@ def _has_python_brace_format(string: str) -> bool: return field_name_seen -def _parse_datetime_header(value: str) -> datetime.datetime: +def _parse_datetime_header(value: str) -> datetime.datetime | None: match = re.match(r'^(?P.*?)(?P[+-]\d{4})?$', value) - dt = datetime.datetime.strptime(match.group('datetime'), '%Y-%m-%d %H:%M') + dt_str = match.group('datetime').strip() + if not dt_str: + # Some tools (e.g. Poedit) leave the PO-Revision-Date/POT-Creation-Date + # header value blank instead of using the conventional + # 'YEAR-MO-DA HO:MI+ZONE' placeholder or omitting the header + # altogether. There's nothing meaningful to parse in that case. + return None + + dt = datetime.datetime.strptime(dt_str, '%Y-%m-%d %H:%M') # Separate the offset into a sign component, hours, and # minutes tzoffset = match.group('tzoffset') @@ -591,11 +599,15 @@ 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) + parsed = _parse_datetime_header(value) + if parsed is not None: + self.creation_date = parsed 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) + parsed = _parse_datetime_header(value) + if parsed is not None: + self.revision_date = parsed @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..8723b0ab9 100644 --- a/tests/messages/test_catalog.py +++ b/tests/messages/test_catalog.py @@ -557,6 +557,29 @@ def test_datetime_parsing(): assert val2.tzinfo is None +def test_datetime_parsing_blank_value(): + # Some tools (e.g. Poedit) leave the PO-Revision-Date/POT-Creation-Date + # header value blank instead of using the conventional + # 'YEAR-MO-DA HO:MI+ZONE' placeholder or omitting the header + # altogether; this used to raise a ValueError. See GH issue #1219. + assert catalog._parse_datetime_header('') is None + assert catalog._parse_datetime_header(' ') is None + + +def test_set_mime_headers_ignores_blank_dates(): + cat = catalog.Catalog() + default_creation_date = cat.creation_date + default_revision_date = cat.revision_date + + cat._set_mime_headers([ + ('POT-Creation-Date', ''), + ('PO-Revision-Date', ''), + ]) + + assert cat.creation_date == default_creation_date + assert cat.revision_date == default_revision_date + + def test_update_catalog_comments(): # Based on https://web.archive.org/web/20100710131029/http://babel.edgewall.org/attachment/ticket/163/cat-update-comments.py