1from collections.abc import Sequence
2from datetime import date, datetime
3from typing import Any, ClassVar
4from xml.etree.ElementTree import Element
5
6from icalendar.compatibility import Self
7from icalendar.error import JCalParsingError, XCalParsingError
8from icalendar.parser import Parameters
9from icalendar.parser.xcal.base import XCalParser
10from icalendar.parser.xcal.wrapper import from_xcal_wrapper
11from icalendar.parser_tools import from_unicode
12
13from .base import TimeBase
14from .types import vDDDTypes
15
16
17class vDDDLists:
18 """A list of vDDDTypes values."""
19
20 default_value: ClassVar[str] = "DATE-TIME"
21 params: Parameters
22 dts: list[vDDDTypes]
23
24 def __init__(self, dt_list, params: dict[str, Any] | None = None) -> None:
25 if params is None:
26 params = {}
27 if not hasattr(dt_list, "__iter__") or (
28 isinstance(dt_list, Sequence) and len(dt_list) == 2 and dt_list[1] is None
29 ):
30 # A ``(dt, None)`` pair (tuple or list) is the rdates form of a single
31 # date, not a list of two values, so don't iterate it element-wise (#1439).
32 dt_list = [dt_list]
33 vddd = []
34 tzid = None
35 for dt_l in dt_list:
36 dt = vDDDTypes(dt_l) if not isinstance(dt_l, vDDDTypes) else dt_l
37 vddd.append(dt)
38 if "TZID" in dt.params:
39 tzid = dt.params["TZID"]
40
41 if tzid:
42 # NOTE: no support for multiple timezones here!
43 params["TZID"] = tzid
44 self.params = Parameters(params)
45 self.dts = vddd
46
47 def to_ical(self):
48 dts_ical = (from_unicode(dt.to_ical()) for dt in self.dts)
49 return b",".join(dts_ical)
50
51 @staticmethod
52 def from_ical(ical, timezone=None):
53 out = []
54 ical_dates = ical.split(",")
55 for ical_dt in ical_dates:
56 out.append(vDDDTypes.from_ical(ical_dt, timezone=timezone))
57 return out
58
59 def __eq__(self, other):
60 if isinstance(other, vDDDLists):
61 return self.dts == other.dts
62 if isinstance(other, (TimeBase, date)):
63 return self.dts == [other]
64 return False
65
66 def __repr__(self):
67 """String representation."""
68 return f"{self.__class__.__name__}({self.dts})"
69
70 @classmethod
71 def examples(cls) -> list[Self]:
72 """Examples of vDDDLists."""
73 return [cls([datetime(2025, 11, 10, 16, 50)])]
74
75 def to_jcal(self, name: str) -> list:
76 """The jCal representation of this property according to :rfc:`7265`."""
77 return [
78 name,
79 self.params.to_jcal(),
80 self.VALUE.lower(),
81 *[dt.to_jcal(name)[3] for dt in self.dts],
82 ]
83
84 def _get_value(self) -> str | None:
85 return None if not self.dts else self.dts[0].VALUE
86
87 from icalendar.param import VALUE
88
89 @classmethod
90 def from_jcal(cls, jcal_property: list) -> Self:
91 """Parse jCal from :rfc:`7265`.
92
93 Parameters:
94 jcal_property: The jCal property to parse.
95
96 Raises:
97 ~error.JCalParsingError: If the jCal provided is invalid.
98 """
99 JCalParsingError.validate_property(jcal_property, cls)
100 values = jcal_property[3:]
101 prop = jcal_property[:3]
102 dts = []
103 for value in values:
104 dts.append(vDDDTypes.from_jcal(prop + [value]))
105 return cls(
106 dts,
107 params=Parameters.from_jcal_property(jcal_property),
108 )
109
110 __hash__ = None
111
112 @property
113 def dt(self) -> TimeBase:
114 """Return the time/date value of the list.
115
116 This is a compatibility method for the vDDDTypes interface.
117
118 Returns:
119 The first value of the list.
120
121 Raises:
122 IndexError: If the list is empty.
123 """
124 return self.dts[0].dt
125
126 def to_xcal(self, element: Element) -> None:
127 """Convert a vDDDTypes to an xCal element."""
128 self.params.to_xcal(element)
129 for dt in self.dts:
130 dt.to_xcal(element)
131
132 @classmethod
133 @from_xcal_wrapper
134 def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self:
135 """Parse xCal from :rfc:`6321`.
136
137 Parameters:
138 xml: The XML to parse or a parser.
139
140 Raises:
141 ~error.XCalParsingError: If the provided xCal is invalid.
142 """
143 dts = []
144 while not parser.is_finished():
145 dts.append(vDDDTypes.from_xcal(parser))
146 if dts:
147 tz = getattr(dts[0].dt, "tzinfo", None)
148 for dt in dts[1:]:
149 if getattr(dt.dt, "tzinfo", None) != tz:
150 raise XCalParsingError(
151 "Cannot mix floating with UTC", None, parser.element
152 )
153 return cls(
154 dts,
155 params=params,
156 )
157
158
159__all__ = ["vDDDLists"]