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
59@overload
60def parse_date_and_datetime(value: None) -> None: ...
61
62
63@overload
64def parse_date_and_datetime(value: str | datetime.date) -> datetime.date: ...
65
66
67def parse_date_and_datetime(value: str | datetime.date | None) -> datetime.date | None:
68    """Coerce str into date and datetime value."""
69    if not isinstance(value, str):
70        return value
71    if "T" in value or " " in value:
72        return datetime.datetime.fromisoformat(value)
73    return datetime.date.fromisoformat(value)
74
75
76def parse_date_and_datetime_list(
77    values: Sequence[str] | Sequence[datetime.date],
78) -> list[datetime.date | datetime.datetime]:
79    """Coerce list[str] into list[date | datetime] values."""
80    if not values:
81        return []
82    if not isinstance(values[0], str):
83        if TYPE_CHECKING:
84            values = cast(list[datetime.date | datetime.datetime], values)
85        return values
86    if TYPE_CHECKING:
87        values = cast(Sequence[str], values)
88    return [
89        datetime.datetime.fromisoformat(val)
90        if "T" in val or " " in val
91        else datetime.date.fromisoformat(val)
92        for val in values
93    ]
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.