1"""TIME property type from :rfc:`5545`."""
2
3import re
4from datetime import datetime, time, timezone
5from typing import Any, ClassVar
6
7from icalendar.compatibility import Self
8from icalendar.error import JCalParsingError
9from icalendar.parser import Parameters
10from icalendar.timezone.tzid import is_utc
11
12from .base import TimeBase
13
14TIME_JCAL_REGEX = re.compile(
15 r"^(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})(?P<utc>Z)?$"
16)
17
18
19class vTime(TimeBase):
20 """Time
21
22 Value Name:
23 TIME
24
25 Purpose:
26 This value type is used to identify values that contain a
27 time of day.
28
29 Format Definition:
30 This value type is defined by the following notation:
31
32 .. code-block:: text
33
34 time = time-hour time-minute time-second [time-utc]
35
36 time-hour = 2DIGIT ;00-23
37 time-minute = 2DIGIT ;00-59
38 time-second = 2DIGIT ;00-60
39 ;The "60" value is used to account for positive "leap" seconds.
40
41 time-utc = "Z"
42
43 Description:
44 If the property permits, multiple "time" values are
45 specified by a COMMA-separated list of values. No additional
46 content value encoding (i.e., BACKSLASH character encoding, see
47 vText) is defined for this value type.
48
49 The "TIME" value type is used to identify values that contain a
50 time of day. The format is based on the [ISO.8601.2004] complete
51 representation, basic format for a time of day. The text format
52 consists of a two-digit, 24-hour of the day (i.e., values 00-23),
53 two-digit minute in the hour (i.e., values 00-59), and two-digit
54 seconds in the minute (i.e., values 00-60). The seconds value of
55 60 MUST only be used to account for positive "leap" seconds.
56 Fractions of a second are not supported by this format.
57
58 In parallel to the "DATE-TIME" definition above, the "TIME" value
59 type expresses time values in three forms:
60
61 The form of time with UTC offset MUST NOT be used. For example,
62 the following is not valid for a time value:
63
64 .. code-block:: text
65
66 230000-0800 ;Invalid time format
67
68 **FORM #1 LOCAL TIME**
69
70 The local time form is simply a time value that does not contain
71 the UTC designator nor does it reference a time zone. For
72 example, 11:00 PM:
73
74 .. code-block:: text
75
76 230000
77
78 Time values of this type are said to be "floating" and are not
79 bound to any time zone in particular. They are used to represent
80 the same hour, minute, and second value regardless of which time
81 zone is currently being observed. For example, an event can be
82 defined that indicates that an individual will be busy from 11:00
83 AM to 1:00 PM every day, no matter which time zone the person is
84 in. In these cases, a local time can be specified. The recipient
85 of an iCalendar object with a property value consisting of a local
86 time, without any relative time zone information, SHOULD interpret
87 the value as being fixed to whatever time zone the "ATTENDEE" is
88 in at any given moment. This means that two "Attendees", may
89 participate in the same event at different UTC times; floating
90 time SHOULD only be used where that is reasonable behavior.
91
92 In most cases, a fixed time is desired. To properly communicate a
93 fixed time in a property value, either UTC time or local time with
94 time zone reference MUST be specified.
95
96 The use of local time in a TIME value without the "TZID" property
97 parameter is to be interpreted as floating time, regardless of the
98 existence of "VTIMEZONE" calendar components in the iCalendar
99 object.
100
101 **FORM #2: UTC TIME**
102
103 UTC time, or absolute time, is identified by a LATIN CAPITAL
104 LETTER Z suffix character, the UTC designator, appended to the
105 time value. For example, the following represents 07:00 AM UTC:
106
107 .. code-block:: text
108
109 070000Z
110
111 The "TZID" property parameter MUST NOT be applied to TIME
112 properties whose time values are specified in UTC.
113
114 **FORM #3: LOCAL TIME AND TIME ZONE REFERENCE**
115
116 The local time with reference to time zone information form is
117 identified by the use the "TZID" property parameter to reference
118 the appropriate time zone definition.
119
120 Example:
121 The following represents 8:30 AM in New York in winter,
122 five hours behind UTC, in each of the three formats:
123
124 .. code-block:: text
125
126 083000
127 133000Z
128 TZID=America/New_York:083000
129 """
130
131 default_value: ClassVar[str] = "TIME"
132 params: Parameters
133
134 def __init__(self, *args, params: dict[str, Any] | None = None):
135 if len(args) == 1:
136 if not isinstance(args[0], (time, datetime)):
137 raise ValueError(f"Expected a datetime.time, got: {args[0]}")
138 self.dt = args[0]
139 else:
140 self.dt = time(*args)
141 self.params = Parameters(params or {})
142 self.params.update_tzid_from(self.dt)
143
144 def to_ical(self):
145 value = self.dt.strftime("%H%M%S")
146 if self.is_utc():
147 value += "Z"
148 return value
149
150 def is_utc(self) -> bool:
151 """Whether this time is UTC."""
152 return self.params.is_utc() or is_utc(self.dt)
153
154 @staticmethod
155 def from_ical(ical):
156 # TODO: timezone support
157 try:
158 timetuple = (int(ical[:2]), int(ical[2:4]), int(ical[4:6]))
159 return time(*timetuple)
160 except Exception as e:
161 raise ValueError(f"Expected time, got: {ical}") from e
162
163 @classmethod
164 def examples(cls) -> list[Self]:
165 """Examples of vTime."""
166 return [cls(time(12, 30))]
167
168 from icalendar.param import VALUE
169
170 def to_jcal(self, name: str) -> list:
171 """The jCal representation of this property according to :rfc:`7265`."""
172 value = self.dt.strftime("%H:%M:%S")
173 if self.is_utc():
174 value += "Z"
175 return [name, self.params.to_jcal(exclude_utc=True), self.VALUE.lower(), value]
176
177 @classmethod
178 def parse_jcal_value(cls, jcal: str) -> time:
179 """Parse a jCal string to a :py:class:`datetime.time`.
180
181 Raises:
182 ~error.JCalParsingError: If it can't parse a time.
183 """
184 JCalParsingError.validate_value_type(jcal, str, cls)
185 match = TIME_JCAL_REGEX.match(jcal)
186 if match is None:
187 raise JCalParsingError("Cannot parse time.", cls, value=jcal)
188 hour = int(match.group("hour"))
189 minute = int(match.group("minute"))
190 second = int(match.group("second"))
191 utc = bool(match.group("utc"))
192 return time(hour, minute, second, tzinfo=timezone.utc if utc else None)
193
194 @classmethod
195 def from_jcal(cls, jcal_property: list) -> Self:
196 """Parse jCal from :rfc:`7265`.
197
198 Parameters:
199 jcal_property: The jCal property to parse.
200
201 Raises:
202 ~error.JCalParsingError: If the provided jCal is invalid.
203 """
204 JCalParsingError.validate_property(jcal_property, cls)
205 with JCalParsingError.reraise_with_path_added(3):
206 value = cls.parse_jcal_value(jcal_property[3])
207 return cls(
208 value,
209 params=Parameters.from_jcal_property(jcal_property),
210 )
211
212
213__all__ = ["vTime"]