"""Canonical UTC :class:`~datetime.datetime` helpers.Two semantics exist across the codebase for handling naive datetimes:* :func:`coerce_utc_aware` — coerce a naive datetime to UTC-aware by attaching :data:`~datetime.UTC`. Used where the source produces naive datetimes (e.g. PKCS#12 certificate timestamps).* :func:`validate_utc_aware` — reject naive datetimes or non-UTC datetimes with :class:`~aeat.core.errors.CoreValidationError`. Used at persistence and model boundaries where a naive datetime indicates a programming error.:func:`parse_iso_datetime` is deliberately policy-neutral: it normalises the``Z`` suffix and returns the parsed :class:`~datetime.datetime`, leaving callersto choose :func:`coerce_utc_aware` or :func:`validate_utc_aware`."""from__future__importannotationsfromdatetimeimportUTC,datetimefrom..errorsimportCoreValidationError
[docs]defparse_iso_datetime(raw:str)->datetime:"""Parse an ISO-8601 datetime string, normalising a trailing ``Z`` to ``+00:00``. :meth:`datetime.fromisoformat` historically rejects the ``Z`` UTC suffix; this normalises it first so UTC-suffixed timestamps parse. The result may be naive or aware depending on the input — pass it through :func:`validate_utc_aware` or :func:`coerce_utc_aware` when an aware value is required. """returndatetime.fromisoformat(raw.replace("Z","+00:00"))
[docs]defcoerce_utc_aware(value:datetime)->datetime:"""Return a UTC-aware datetime, coercing a naive one if necessary. Naive datetimes have :data:`datetime.UTC` attached. Timezone-aware datetimes whose offset differs from UTC are converted via :meth:`datetime.astimezone`. Args: value: Any :class:`datetime` instance, naive or aware. Returns: A UTC-aware :class:`datetime` instance. """ifvalue.tzinfoisNone:returnvalue.replace(tzinfo=UTC)returnvalue.astimezone(UTC)
[docs]defvalidate_utc_aware(value:datetime)->datetime:"""Return *value* unchanged if it is a UTC-aware datetime. Raises :class:`aeat.core.errors.CoreValidationError` when *value* is naive (no ``tzinfo``) or when its UTC offset is not zero (i.e. not UTC). Args: value: The datetime to validate. Returns: *value* unchanged. Raises: CoreValidationError: When *value* is naive or not in UTC. """ifvalue.tzinfoisNone:raiseCoreValidationError("datetime must be timezone-aware UTC")ifvalue.utcoffset()!=UTC.utcoffset(value):raiseCoreValidationError("datetime must be in UTC")returnvalue