Skip to content

from_format raises AttributeError for ordinal tokens (Do in locales without ordinals; Qo/Mo/DDDo/e/eo/do/Wo/wo everywhere) #1074

Open

Description

@rahul05ranjan

Summary

pendulum.from_format cannot parse any of the ordinal tokens, raising a raw AttributeError instead of a ValueError — and for Do the crash is locale-dependent:

1. Do in locales without ordinal suffixes (bg, de, ru, pl, uk, da, sv, ...) — the crash comes from Formatter._LOCALIZABLE_TOKENS:

>>> pendulum.datetime(1982, 6, 7).format("Do MMMM YYYY", locale="de")
'7 Juni 1982'                                    # format() is fine
>>> pendulum.from_format("7 Juni 1982", "Do MMMM YYYY", locale="de")
AttributeError: 'NoneType' object has no attribute 'values'

Locale.get("custom.ordinal") returns None for these locales and the Do entry does locale.get("custom.ordinal").values(). Note Locale.ordinalize() (used by format()) already falls back to the plain number when the locale has no ordinals, so the parse pattern should accept plain digits — exactly what format() emits.

2. Qo, Mo, DDDo, do, e, eo, Wo, wo in every locale — these tokens are None in _LOCALIZABLE_TOKENS, so _replace_tokens does locale.translation(None).values():

>>> pendulum.datetime(1982, 6, 7).format("Qo YYYY", locale="en")
'2nd 1982'
>>> pendulum.from_format("2nd 1982", "Qo YYYY", locale="en")
AttributeError: 'NoneType' object has no attribute 'values'

The AttributeError leaks from a public API instead of the regular ValueError("Unsupported token: ...") that _replace_tokens is designed to raise for tokens without candidates.

Root cause

src/pendulum/formatting/formatter.py:

  • The Do lambda calls .values() on locale.get("custom.ordinal") without a None guard (line ~79-81).
  • The None entries for the other ordinal tokens are passed to locale.translation(None) which returns None (lines ~70-77), and .values() crashes. The intended ValueError(f"Unsupported token: {token}") guard at the bottom of _replace_tokens is never reached.

Expected behavior

  • from_format must accept whatever format() emits: for Do in a locale without ordinals that means plain digits (\d+); for locales with ordinals the existing \d+<suffix> patterns.
  • Tokens that are deliberately not parse-supported should raise the existing ValueError("Unsupported token: X"), not AttributeError.
  • Mo, Qo and DDDo are straightforwardly parseable (month / quarter / day of year — _check_parsed already consumes all three fields), so they get real support, giving format/from_format round-trips for them.

Environment

  • pendulum 3.1.0 (current master, e06dace), Python 3.13, Linux.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions