Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/dt/time.py: 73%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

79 statements  

1"""TIME property type from :rfc:`5545`.""" 

2 

3import re 

4from datetime import datetime, time, timezone, tzinfo 

5from typing import Any, ClassVar 

6 

7from icalendar.compatibility import Self 

8from icalendar.error import JCalParsingError 

9from icalendar.parser import Parameters 

10from icalendar.timezone import tzp 

11from icalendar.timezone.tzid import is_utc 

12 

13from .base import TimeBase 

14 

15TIME_JCAL_REGEX = re.compile( 

16 r"^(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})(?P<utc>Z)?\Z" 

17) 

18 

19 

20class vTime(TimeBase): 

21 """Time 

22 

23 Value Name: 

24 TIME 

25 

26 Purpose: 

27 This value type is used to identify values that contain a 

28 time of day. 

29 

30 Format Definition: 

31 This value type is defined by the following notation: 

32 

33 .. code-block:: text 

34 

35 time = time-hour time-minute time-second [time-utc] 

36 

37 time-hour = 2DIGIT ;00-23 

38 time-minute = 2DIGIT ;00-59 

39 time-second = 2DIGIT ;00-60 

40 ;The "60" value is used to account for positive "leap" seconds. 

41 

42 time-utc = "Z" 

43 

44 Description: 

45 If the property permits, multiple "time" values are 

46 specified by a COMMA-separated list of values. No additional 

47 content value encoding (i.e., BACKSLASH character encoding, see 

48 vText) is defined for this value type. 

49 

50 The "TIME" value type is used to identify values that contain a 

51 time of day. The format is based on the [ISO.8601.2004] complete 

52 representation, basic format for a time of day. The text format 

53 consists of a two-digit, 24-hour of the day (i.e., values 00-23), 

54 two-digit minute in the hour (i.e., values 00-59), and two-digit 

55 seconds in the minute (i.e., values 00-60). The seconds value of 

56 60 MUST only be used to account for positive "leap" seconds. 

57 Fractions of a second are not supported by this format. 

58 

59 In parallel to the "DATE-TIME" definition above, the "TIME" value 

60 type expresses time values in three forms: 

61 

62 The form of time with UTC offset MUST NOT be used. For example, 

63 the following is not valid for a time value: 

64 

65 .. code-block:: ics 

66 

67 230000-0800 ;Invalid time format 

68 

69 **FORM #1 LOCAL TIME** 

70 

71 The local time form is simply a time value that does not contain 

72 the UTC designator nor does it reference a time zone. For 

73 example, 11:00 PM: 

74 

75 .. code-block:: ics 

76 

77 230000 

78 

79 Time values of this type are said to be "floating" and are not 

80 bound to any time zone in particular. They are used to represent 

81 the same hour, minute, and second value regardless of which time 

82 zone is currently being observed. For example, an event can be 

83 defined that indicates that an individual will be busy from 11:00 

84 AM to 1:00 PM every day, no matter which time zone the person is 

85 in. In these cases, a local time can be specified. The recipient 

86 of an iCalendar object with a property value consisting of a local 

87 time, without any relative time zone information, SHOULD interpret 

88 the value as being fixed to whatever time zone the "ATTENDEE" is 

89 in at any given moment. This means that two "Attendees", may 

90 participate in the same event at different UTC times; floating 

91 time SHOULD only be used where that is reasonable behavior. 

92 

93 In most cases, a fixed time is desired. To properly communicate a 

94 fixed time in a property value, either UTC time or local time with 

95 time zone reference MUST be specified. 

96 

97 The use of local time in a TIME value without the "TZID" property 

98 parameter is to be interpreted as floating time, regardless of the 

99 existence of "VTIMEZONE" calendar components in the iCalendar 

100 object. 

101 

102 **FORM #2: UTC TIME** 

103 

104 UTC time, or absolute time, is identified by a LATIN CAPITAL 

105 LETTER Z suffix character, the UTC designator, appended to the 

106 time value. For example, the following represents 07:00 AM UTC: 

107 

108 .. code-block:: ics 

109 

110 070000Z 

111 

112 The "TZID" property parameter MUST NOT be applied to TIME 

113 properties whose time values are specified in UTC. 

114 

115 **FORM #3: LOCAL TIME AND TIME ZONE REFERENCE** 

116 

117 The local time with reference to time zone information form is 

118 identified by the use the "TZID" property parameter to reference 

119 the appropriate time zone definition. 

120 

121 Example: 

122 The following represents 8:30 AM in New York in winter, 

123 five hours behind UTC, in each of the three formats: 

124 

125 .. code-block:: ics 

126 

127 083000 

128 133000Z 

129 TZID=America/New_York:083000 

130 """ 

131 

132 default_value: ClassVar[str] = "TIME" 

133 params: Parameters 

134 

135 def __init__(self, *args: Any, params: dict[str, Any] | None = None) -> None: 

136 if len(args) == 1: 

137 if not isinstance(args[0], (time, datetime)): 

138 raise ValueError(f"Expected a datetime.time, got: {args[0]}") 

139 self.dt = args[0] 

140 else: 

141 self.dt = time(*args) 

142 self.params = Parameters(params or {}) 

143 self.params.update_tzid_from(self.dt) 

144 

145 def to_ical(self): 

146 value = self.dt.strftime("%H%M%S") 

147 if self.is_utc(): 

148 value += "Z" 

149 return value 

150 

151 def is_utc(self) -> bool: 

152 """Whether this time is UTC.""" 

153 return self.params.is_utc() or is_utc(self.dt) 

154 

155 @staticmethod 

156 def from_ical(ical: str, timezone: str | None | tzinfo = None) -> time: 

157 """Convert an ical string into a time. 

158 

159 This method supports parsing the three forms of time values defined in :rfc:`5545#section-3.3.12`: 

160 - Local time (floating) 

161 - UTC time 

162 - Local time with time zone reference 

163 

164 Returns: 

165 A :class:`datetime.time` object representing the parsed time, with timezone information if applicable. 

166 

167 Raises: 

168 ValueError: if the provided string cannot be parsed as a time. 

169 """ 

170 tzinfo = None 

171 if isinstance(timezone, str): 

172 tzinfo = tzp.timezone(timezone) 

173 elif timezone is not None: 

174 tzinfo = timezone 

175 

176 if isinstance(ical, bytes): 

177 ical = ical.decode() 

178 # Extract the value part if parameters are present per 

179 # https://datatracker.ietf.org/doc/html/rfc5545.html#section-3.3.5 

180 # Form #3: TZID=America/New_York:083000 

181 ical = ical.rpartition(":")[2] 

182 if utc := ical.endswith("Z"): 

183 ical = ical[:-1] 

184 # time = time-hour time-minute time-second [time-utc], six digits, 

185 # per https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.12 

186 if len(ical) != 6 or not ical.isascii() or not ical.isdigit(): 

187 raise ValueError(f"Expected time, got: {ical}") 

188 try: 

189 timetuple = (int(ical[:2]), int(ical[2:4]), int(ical[4:6])) 

190 if tzinfo: 

191 return tzp.localize(time(*timetuple), tzinfo) 

192 if utc: 

193 return tzp.localize_utc(time(*timetuple)) 

194 return time(*timetuple) 

195 except Exception as e: 

196 raise ValueError(f"Expected time, got: {ical}") from e 

197 

198 @classmethod 

199 def examples(cls) -> list[Self]: 

200 """Examples of vTime.""" 

201 return [cls(time(12, 30))] 

202 

203 from icalendar.param import VALUE 

204 

205 def to_jcal(self, name: str) -> list: 

206 """The jCal representation of this property according to :rfc:`7265`.""" 

207 value = self.dt.strftime("%H:%M:%S") 

208 if self.is_utc(): 

209 value += "Z" 

210 return [name, self.params.to_jcal(exclude_utc=True), self.VALUE.lower(), value] 

211 

212 @classmethod 

213 def parse_jcal_value(cls, jcal: str) -> time: 

214 """Parse a jCal string to a :class:`datetime.time`. 

215 

216 Raises: 

217 ~error.JCalParsingError: If it can't parse a time. 

218 """ 

219 JCalParsingError.validate_value_type(jcal, str, cls) 

220 match = TIME_JCAL_REGEX.match(jcal) 

221 if match is None: 

222 raise JCalParsingError("Cannot parse time.", cls, value=jcal) 

223 hour = int(match.group("hour")) 

224 minute = int(match.group("minute")) 

225 second = int(match.group("second")) 

226 utc = bool(match.group("utc")) 

227 return time(hour, minute, second, tzinfo=timezone.utc if utc else None) 

228 

229 @classmethod 

230 def from_jcal(cls, jcal_property: list) -> Self: 

231 """Parse jCal from :rfc:`7265`. 

232 

233 Parameters: 

234 jcal_property: The jCal property to parse. 

235 

236 Raises: 

237 ~error.JCalParsingError: If the provided jCal is invalid. 

238 """ 

239 JCalParsingError.validate_property(jcal_property, cls) 

240 with JCalParsingError.reraise_with_path_added(3): 

241 value = cls.parse_jcal_value(jcal_property[3]) 

242 return cls( 

243 value, 

244 params=Parameters.from_jcal_property(jcal_property), 

245 ) 

246 

247 

248__all__ = ["vTime"]