File: date_time.py

package info (click to toggle)
python-ical 9.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,448 kB
  • sloc: python: 13,877; sh: 9; makefile: 5
file content (103 lines) | stat: -rw-r--r-- 3,541 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""Library for parsing and encoding DATE-TIME types."""

from __future__ import annotations

import datetime
import logging
import re
import zoneinfo
from typing import Any

from ical.parsing.property import ParsedProperty, ParsedPropertyParameter

from .data_types import DATA_TYPE

_LOGGER = logging.getLogger(__name__)


DATETIME_REGEX = re.compile(r"^([0-9]{8})T([0-9]{6})(Z)?$")
TZID = "TZID"
ATTR_VALUE = "VALUE"


def parse_property_value(
    prop: ParsedProperty, allow_invalid_timezone: bool = False
) -> datetime.datetime:
    """Parse a rfc5545 into a datetime.datetime."""
    if not (match := DATETIME_REGEX.fullmatch(prop.value)):
        raise ValueError(f"Expected value to match DATE-TIME pattern: {prop.value}")

    # Example: TZID=America/New_York:19980119T020000
    timezone: datetime.tzinfo | None = None
    if param := prop.get_parameter(TZID):
        if param.values and (value := param.values[0]):
            if isinstance(value, datetime.tzinfo):
                timezone = value
            else:
                try:
                    timezone = zoneinfo.ZoneInfo(value)
                except zoneinfo.ZoneInfoNotFoundError:
                    if allow_invalid_timezone:
                        timezone = None
                    raise ValueError(
                        f"Expected DATE-TIME TZID value '{value}' to be valid timezone"
                    )
    elif match.group(3):  # Example: 19980119T070000Z
        timezone = datetime.timezone.utc

    # Example: 19980118T230000
    date_value = match.group(1)
    year = int(date_value[0:4])
    month = int(date_value[4:6])
    day = int(date_value[6:])
    time_value = match.group(2)
    hour = int(time_value[0:2])
    minute = int(time_value[2:4])
    second = int(time_value[4:6])

    result = datetime.datetime(year, month, day, hour, minute, second, tzinfo=timezone)
    _LOGGER.debug("DateTimeEncoder returned %s", result)
    return result


@DATA_TYPE.register("DATE-TIME", parse_order=2)
class DateTimeEncoder:
    """Class to handle encoding for a datetime.datetime."""

    @classmethod
    def __property_type__(cls) -> type:
        return datetime.datetime

    @classmethod
    def __parse_property_value__(cls, prop: ParsedProperty) -> datetime.datetime:
        """Parse a rfc5545 into a datetime.datetime."""
        return parse_property_value(prop, allow_invalid_timezone=False)

    @classmethod
    def __encode_property_json__(cls, value: datetime.datetime) -> str | dict[str, str]:
        """Encode an ICS value during json serializaton."""
        if value.tzinfo is None:
            return value.strftime("%Y%m%dT%H%M%S")
        # Does not yet handle timezones and encoding property parameters
        if not value.utcoffset():
            return value.strftime("%Y%m%dT%H%M%SZ")
        return {
            ATTR_VALUE: value.strftime("%Y%m%dT%H%M%S"),
            TZID: str(value.tzinfo),  # Timezone key
        }

    @classmethod
    def __encode_property_value__(cls, value: str | dict[str, Any]) -> str | None:
        """Encode the ParsedProperty value."""
        if isinstance(value, str):
            return value
        return value.get(ATTR_VALUE)

    @classmethod
    def __encode_property_params__(
        cls, value: str | dict[str, str]
    ) -> list[ParsedPropertyParameter]:
        """Encode parameters for the property value."""
        if isinstance(value, dict) and (tzid := value.get(TZID)):
            return [ParsedPropertyParameter(name=TZID, values=[str(tzid)])]
        return []