time_range.py 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. """DMS查询接口统一的带时区ISO 8601时间范围解析。"""
  2. from __future__ import annotations
  3. from datetime import datetime, timezone
  4. from typing import Mapping
  5. from dms.common.errors import InvalidArgumentError
  6. def parse_datetime(value: str | None, *, name: str) -> datetime | None:
  7. """解析带明确时区的ISO 8601时间并转换为UTC无时区值。"""
  8. if value is None:
  9. return None
  10. if not isinstance(value, str) or not value.strip():
  11. raise InvalidArgumentError(f"{name}必须是包含时区的ISO 8601时间")
  12. normalized = value.strip()
  13. if normalized.endswith(("Z", "z")):
  14. normalized = normalized[:-1] + "+00:00"
  15. try:
  16. parsed = datetime.fromisoformat(normalized)
  17. except ValueError as exc:
  18. raise InvalidArgumentError(
  19. f"{name}必须是包含时区的ISO 8601时间"
  20. ) from exc
  21. if parsed.tzinfo is None or parsed.utcoffset() is None:
  22. raise InvalidArgumentError(f"{name}必须包含明确时区")
  23. return parsed.astimezone(timezone.utc).replace(tzinfo=None)
  24. def parse_updated_range(
  25. params: Mapping[str, str],
  26. ) -> tuple[datetime | None, datetime | None]:
  27. """解析updatedFrom/updatedTo并校验闭区间方向。"""
  28. updated_from = parse_datetime(params.get("updatedFrom"), name="updatedFrom")
  29. updated_to = parse_datetime(params.get("updatedTo"), name="updatedTo")
  30. if (
  31. updated_from is not None
  32. and updated_to is not None
  33. and updated_from > updated_to
  34. ):
  35. raise InvalidArgumentError("updatedFrom不得晚于updatedTo")
  36. return updated_from, updated_to
  37. __all__ = ["parse_datetime", "parse_updated_range"]