ical.util

Utility methods used by multiple components.

  1"""Utility methods used by multiple components."""
  2
  3from __future__ import annotations
  4
  5from collections.abc import Sequence
  6import datetime
  7from importlib import metadata
  8from types import NoneType
  9from typing import TYPE_CHECKING, Any, Union, cast, get_args, get_origin, overload
 10import uuid
 11
 12__all__ = [
 13    "dtstamp_factory",
 14    "uid_factory",
 15    "prodid_factory",
 16]
 17
 18
 19MIDNIGHT = datetime.time()
 20PRODID = "github.com/allenporter/ical"
 21VERSION = metadata.version("ical")
 22
 23
 24def dtstamp_factory() -> datetime.datetime:
 25    """Factory method for new event timestamps to facilitate mocking."""
 26    return datetime.datetime.now(tz=datetime.UTC)
 27
 28
 29def uid_factory() -> str:
 30    """Factory method for new uids to facilitate mocking."""
 31    return str(uuid.uuid1())
 32
 33
 34def prodid_factory() -> str:
 35    """Return the ical version to facilitate mocking."""
 36    return f"-//{PRODID}//{VERSION}//EN"
 37
 38
 39def local_timezone() -> datetime.tzinfo:
 40    """Get the local timezone to use when converting date to datetime."""
 41    if local_tz := datetime.datetime.now().astimezone().tzinfo:
 42        return local_tz
 43    return datetime.timezone.utc
 44
 45
 46def normalize_datetime(
 47    value: datetime.date | datetime.datetime, tzinfo: datetime.tzinfo | None = None
 48) -> datetime.datetime:
 49    """Convert date or datetime to a value that can be used for comparison."""
 50    if not isinstance(value, datetime.datetime):
 51        value = datetime.datetime.combine(value, MIDNIGHT)
 52    if value.tzinfo is None:
 53        if tzinfo is None:
 54            tzinfo = local_timezone()
 55        value = value.replace(tzinfo=tzinfo)
 56    return value
 57
 58
 59def get_field_type(annotation: Any) -> Any:
 60    """Filter Optional type, e.g. for 'Optional[int]' return 'int'."""
 61    if get_origin(annotation) is Union:
 62        args: Sequence[Any] = get_args(annotation)
 63        if len(args) == 2:
 64            args = [arg for arg in args if arg is not NoneType]
 65            if len(args) == 1:
 66                return args[0]
 67    return annotation
 68
 69
 70@overload
 71def parse_date_and_datetime(value: None) -> None: ...
 72
 73@overload
 74def parse_date_and_datetime(value: str | datetime.date) -> datetime.date: ...
 75
 76def parse_date_and_datetime(value: str | datetime.date | None) -> datetime.date | None:
 77    """Coerce str into date and datetime value."""
 78    if not isinstance(value, str):
 79        return value
 80    if "T" in value or " " in value:
 81        return datetime.datetime.fromisoformat(value)
 82    return datetime.date.fromisoformat(value)
 83
 84
 85def parse_date_and_datetime_list(
 86    values: Sequence[str] | Sequence[datetime.date]
 87) -> list[datetime.date | datetime.datetime]:
 88    """Coerce list[str] into list[date | datetime] values."""
 89    if not values:
 90        return []
 91    if not isinstance(values[0], str):
 92        if TYPE_CHECKING:
 93            values = cast(list[datetime.date | datetime.datetime], values)
 94        return values
 95    if TYPE_CHECKING:
 96        values = cast(Sequence[str], values)
 97    return [
 98        datetime.datetime.fromisoformat(val)
 99        if "T" in val or " " in val
100        else datetime.date.fromisoformat(val)
101        for val in values
102    ]
def dtstamp_factory() -> datetime.datetime:
25def dtstamp_factory() -> datetime.datetime:
26    """Factory method for new event timestamps to facilitate mocking."""
27    return datetime.datetime.now(tz=datetime.UTC)

Factory method for new event timestamps to facilitate mocking.

def uid_factory() -> str:
30def uid_factory() -> str:
31    """Factory method for new uids to facilitate mocking."""
32    return str(uuid.uuid1())

Factory method for new uids to facilitate mocking.

def prodid_factory() -> str:
35def prodid_factory() -> str:
36    """Return the ical version to facilitate mocking."""
37    return f"-//{PRODID}//{VERSION}//EN"

Return the ical version to facilitate mocking.