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.