Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/__init__.py: 81%

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

734 statements  

1"""This module contains the parser/generators (or coders/encoders if you 

2prefer) for the classes/datatypes that are used in iCalendar: 

3 

4########################################################################### 

5 

6# This module defines these property value data types and property parameters 

7 

84.2 Defined property parameters are: 

9 

10.. code-block:: text 

11 

12 ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE, 

13 FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP, 

14 SENT-BY, TZID, VALUE 

15 

164.3 Defined value data types are: 

17 

18.. code-block:: text 

19 

20 BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER, 

21 PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET 

22 

23########################################################################### 

24 

25iCalendar properties have values. The values are strongly typed. This module 

26defines these types, calling val.to_ical() on them will render them as defined 

27in rfc5545. 

28 

29If you pass any of these classes a Python primitive, you will have an object 

30that can render itself as iCalendar formatted date. 

31 

32Property Value Data Types start with a 'v'. they all have an to_ical() and 

33from_ical() method. The to_ical() method generates a text string in the 

34iCalendar format. The from_ical() method can parse this format and return a 

35primitive Python datatype. So it should always be true that: 

36 

37.. code-block:: python 

38 

39 x == vDataType.from_ical(VDataType(x).to_ical()) 

40 

41These types are mainly used for parsing and file generation. But you can set 

42them directly. 

43""" 

44 

45from __future__ import annotations 

46 

47import base64 

48import binascii 

49import re 

50from datetime import date, datetime, time, timedelta 

51from typing import Any, Union 

52 

53from icalendar.caselessdict import CaselessDict 

54from icalendar.enums import Enum 

55from icalendar.parser import Parameters, escape_char, unescape_char 

56from icalendar.parser_tools import ( 

57 DEFAULT_ENCODING, 

58 ICAL_TYPE, 

59 SEQUENCE_TYPES, 

60 from_unicode, 

61 to_unicode, 

62) 

63from icalendar.timezone import tzid_from_dt, tzid_from_tzinfo, tzp 

64from icalendar.tools import to_datetime 

65 

66DURATION_REGEX = re.compile( 

67 r"([-+]?)P(?:(\d+)W)?(?:(\d+)D)?" r"(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$" 

68) 

69 

70WEEKDAY_RULE = re.compile( 

71 r"(?P<signal>[+-]?)(?P<relative>[\d]{0,2})" r"(?P<weekday>[\w]{2})$" 

72) 

73 

74 

75class vBinary: 

76 """Binary property values are base 64 encoded.""" 

77 

78 params: Parameters 

79 obj: str 

80 

81 def __init__(self, obj, params: dict[str, str] | None = None): 

82 self.obj = to_unicode(obj) 

83 self.params = Parameters(encoding="BASE64", value="BINARY") 

84 if params: 

85 self.params.update(params) 

86 

87 def __repr__(self): 

88 return f"vBinary({self.to_ical()})" 

89 

90 def to_ical(self): 

91 return binascii.b2a_base64(self.obj.encode("utf-8"))[:-1] 

92 

93 @staticmethod 

94 def from_ical(ical): 

95 try: 

96 return base64.b64decode(ical) 

97 except ValueError as e: 

98 raise ValueError("Not valid base 64 encoding.") from e 

99 

100 def __eq__(self, other): 

101 """self == other""" 

102 return isinstance(other, vBinary) and self.obj == other.obj 

103 

104 

105class vBoolean(int): 

106 """Boolean 

107 

108 Value Name: BOOLEAN 

109 

110 Purpose: This value type is used to identify properties that contain 

111 either a "TRUE" or "FALSE" Boolean value. 

112 

113 Format Definition: This value type is defined by the following 

114 notation: 

115 

116 .. code-block:: text 

117 

118 boolean = "TRUE" / "FALSE" 

119 

120 Description: These values are case-insensitive text. No additional 

121 content value encoding is defined for this value type. 

122 

123 Example: The following is an example of a hypothetical property that 

124 has a BOOLEAN value type: 

125 

126 .. code-block:: python 

127 

128 TRUE 

129 

130 .. code-block:: pycon 

131 

132 >>> from icalendar.prop import vBoolean 

133 >>> boolean = vBoolean.from_ical('TRUE') 

134 >>> boolean 

135 True 

136 >>> boolean = vBoolean.from_ical('FALSE') 

137 >>> boolean 

138 False 

139 >>> boolean = vBoolean.from_ical('True') 

140 >>> boolean 

141 True 

142 """ 

143 

144 params: Parameters 

145 

146 BOOL_MAP = CaselessDict({"true": True, "false": False}) 

147 

148 def __new__(cls, *args, params: dict[str, Any] | None = None, **kwargs): 

149 if params is None: 

150 params = {} 

151 self = super().__new__(cls, *args, **kwargs) 

152 self.params = Parameters(params) 

153 return self 

154 

155 def to_ical(self): 

156 return b"TRUE" if self else b"FALSE" 

157 

158 @classmethod 

159 def from_ical(cls, ical): 

160 try: 

161 return cls.BOOL_MAP[ical] 

162 except Exception as e: 

163 raise ValueError(f"Expected 'TRUE' or 'FALSE'. Got {ical}") from e 

164 

165 

166class vText(str): 

167 """Simple text.""" 

168 

169 params: Parameters 

170 __slots__ = ("encoding", "params") 

171 

172 def __new__( 

173 cls, 

174 value, 

175 encoding=DEFAULT_ENCODING, 

176 /, 

177 params: dict[str, Any] | None = None, 

178 ): 

179 if params is None: 

180 params = {} 

181 value = to_unicode(value, encoding=encoding) 

182 self = super().__new__(cls, value) 

183 self.encoding = encoding 

184 self.params = Parameters(params) 

185 return self 

186 

187 def __repr__(self) -> str: 

188 return f"vText({self.to_ical()!r})" 

189 

190 def to_ical(self) -> bytes: 

191 return escape_char(self).encode(self.encoding) 

192 

193 @classmethod 

194 def from_ical(cls, ical: ICAL_TYPE): 

195 ical_unesc = unescape_char(ical) 

196 return cls(ical_unesc) 

197 

198 from icalendar.param import ALTREP, LANGUAGE, RELTYPE 

199 

200 

201class vCalAddress(str): 

202 r"""Calendar User Address 

203 

204 Value Name: 

205 CAL-ADDRESS 

206 

207 Purpose: 

208 This value type is used to identify properties that contain a 

209 calendar user address. 

210 

211 Description: 

212 The value is a URI as defined by [RFC3986] or any other 

213 IANA-registered form for a URI. When used to address an Internet 

214 email transport address for a calendar user, the value MUST be a 

215 mailto URI, as defined by [RFC2368]. 

216 

217 Example: 

218 ``mailto:`` is in front of the address. 

219 

220 .. code-block:: text 

221 

222 mailto:jane_doe@example.com 

223 

224 Parsing: 

225 

226 .. code-block:: pycon 

227 

228 >>> from icalendar import vCalAddress 

229 >>> cal_address = vCalAddress.from_ical('mailto:jane_doe@example.com') 

230 >>> cal_address 

231 vCalAddress('mailto:jane_doe@example.com') 

232 

233 Encoding: 

234 

235 .. code-block:: pycon 

236 

237 >>> from icalendar import vCalAddress, Event 

238 >>> event = Event() 

239 >>> jane = vCalAddress("mailto:jane_doe@example.com") 

240 >>> jane.name = "Jane" 

241 >>> event["organizer"] = jane 

242 >>> print(event.to_ical().decode().replace('\\r\\n', '\\n').strip()) 

243 BEGIN:VEVENT 

244 ORGANIZER;CN=Jane:mailto:jane_doe@example.com 

245 END:VEVENT 

246 """ 

247 

248 params: Parameters 

249 __slots__ = ("params",) 

250 

251 def __new__( 

252 cls, 

253 value, 

254 encoding=DEFAULT_ENCODING, 

255 /, 

256 params: dict[str, Any] | None = None, 

257 ): 

258 if params is None: 

259 params = {} 

260 value = to_unicode(value, encoding=encoding) 

261 self = super().__new__(cls, value) 

262 self.params = Parameters(params) 

263 return self 

264 

265 def __repr__(self): 

266 return f"vCalAddress('{self}')" 

267 

268 def to_ical(self): 

269 return self.encode(DEFAULT_ENCODING) 

270 

271 @classmethod 

272 def from_ical(cls, ical): 

273 return cls(ical) 

274 

275 @property 

276 def email(self) -> str: 

277 """The email address without mailto: at the start.""" 

278 if self.lower().startswith("mailto:"): 

279 return self[7:] 

280 return str(self) 

281 

282 from icalendar.param import ( 

283 CN, 

284 CUTYPE, 

285 DELEGATED_FROM, 

286 DELEGATED_TO, 

287 DIR, 

288 LANGUAGE, 

289 PARTSTAT, 

290 ROLE, 

291 RSVP, 

292 SENT_BY, 

293 ) 

294 

295 name = CN 

296 

297 @staticmethod 

298 def _get_email(email: str) -> str: 

299 """Extract email and add mailto: prefix if needed. 

300 

301 Handles case-insensitive mailto: prefix checking. 

302 

303 Args: 

304 email: Email string that may or may not have mailto: prefix 

305 

306 Returns: 

307 Email string with mailto: prefix 

308 """ 

309 if not email.lower().startswith("mailto:"): 

310 return f"mailto:{email}" 

311 return email 

312 

313 @classmethod 

314 def new( 

315 cls, 

316 email: str, 

317 /, 

318 cn: str | None = None, 

319 cutype: str | None = None, 

320 delegated_from: str | None = None, 

321 delegated_to: str | None = None, 

322 directory: str | None = None, 

323 language: str | None = None, 

324 partstat: str | None = None, 

325 role: str | None = None, 

326 rsvp: bool | None = None, 

327 sent_by: str | None = None, 

328 ): 

329 """Create a new vCalAddress with RFC 5545 parameters. 

330 

331 Creates a vCalAddress instance with automatic mailto: prefix handling 

332 and support for all standard RFC 5545 parameters. 

333 

334 Args: 

335 email: The email address (mailto: prefix added automatically if missing) 

336 cn: Common Name parameter 

337 cutype: Calendar user type (INDIVIDUAL, GROUP, RESOURCE, ROOM) 

338 delegated_from: Email of the calendar user that delegated 

339 delegated_to: Email of the calendar user that was delegated to 

340 directory: Reference to directory information 

341 language: Language for text values 

342 partstat: Participation status (NEEDS-ACTION, ACCEPTED, DECLINED, etc.) 

343 role: Role (REQ-PARTICIPANT, OPT-PARTICIPANT, NON-PARTICIPANT, CHAIR) 

344 rsvp: Whether RSVP is requested 

345 sent_by: Email of the calendar user acting on behalf of this user 

346 

347 Returns: 

348 vCalAddress: A new calendar address with specified parameters 

349 

350 Raises: 

351 TypeError: If email is not a string 

352 

353 Examples: 

354 Basic usage: 

355 

356 >>> from icalendar.prop import vCalAddress 

357 >>> addr = vCalAddress.new("test@test.com") 

358 >>> str(addr) 

359 'mailto:test@test.com' 

360 

361 With parameters: 

362 

363 >>> addr = vCalAddress.new("test@test.com", cn="Test User", role="CHAIR") 

364 >>> addr.params["CN"] 

365 'Test User' 

366 >>> addr.params["ROLE"] 

367 'CHAIR' 

368 """ 

369 if not isinstance(email, str): 

370 raise TypeError(f"Email must be a string, not {type(email).__name__}") 

371 

372 # Handle mailto: prefix (case-insensitive) 

373 email_with_prefix = cls._get_email(email) 

374 

375 # Create the address 

376 addr = cls(email_with_prefix) 

377 

378 # Set parameters if provided 

379 if cn is not None: 

380 addr.params["CN"] = cn 

381 if cutype is not None: 

382 addr.params["CUTYPE"] = cutype 

383 if delegated_from is not None: 

384 addr.params["DELEGATED-FROM"] = cls._get_email(delegated_from) 

385 if delegated_to is not None: 

386 addr.params["DELEGATED-TO"] = cls._get_email(delegated_to) 

387 if directory is not None: 

388 addr.params["DIR"] = directory 

389 if language is not None: 

390 addr.params["LANGUAGE"] = language 

391 if partstat is not None: 

392 addr.params["PARTSTAT"] = partstat 

393 if role is not None: 

394 addr.params["ROLE"] = role 

395 if rsvp is not None: 

396 addr.params["RSVP"] = "TRUE" if rsvp else "FALSE" 

397 if sent_by is not None: 

398 addr.params["SENT-BY"] = cls._get_email(sent_by) 

399 

400 return addr 

401 

402 

403class vFloat(float): 

404 """Float 

405 

406 Value Name: 

407 FLOAT 

408 

409 Purpose: 

410 This value type is used to identify properties that contain 

411 a real-number value. 

412 

413 Format Definition: 

414 This value type is defined by the following notation: 

415 

416 .. code-block:: text 

417 

418 float = (["+"] / "-") 1*DIGIT ["." 1*DIGIT] 

419 

420 Description: 

421 If the property permits, multiple "float" values are 

422 specified by a COMMA-separated list of values. 

423 

424 Example: 

425 

426 .. code-block:: text 

427 

428 1000000.0000001 

429 1.333 

430 -3.14 

431 

432 .. code-block:: pycon 

433 

434 >>> from icalendar.prop import vFloat 

435 >>> float = vFloat.from_ical('1000000.0000001') 

436 >>> float 

437 1000000.0000001 

438 >>> float = vFloat.from_ical('1.333') 

439 >>> float 

440 1.333 

441 >>> float = vFloat.from_ical('+1.333') 

442 >>> float 

443 1.333 

444 >>> float = vFloat.from_ical('-3.14') 

445 >>> float 

446 -3.14 

447 """ 

448 

449 params: Parameters 

450 

451 def __new__(cls, *args, params: dict[str, Any] | None = None, **kwargs): 

452 if params is None: 

453 params = {} 

454 self = super().__new__(cls, *args, **kwargs) 

455 self.params = Parameters(params) 

456 return self 

457 

458 def to_ical(self): 

459 return str(self).encode("utf-8") 

460 

461 @classmethod 

462 def from_ical(cls, ical): 

463 try: 

464 return cls(ical) 

465 except Exception as e: 

466 raise ValueError(f"Expected float value, got: {ical}") from e 

467 

468 

469class vInt(int): 

470 """Integer 

471 

472 Value Name: 

473 INTEGER 

474 

475 Purpose: 

476 This value type is used to identify properties that contain a 

477 signed integer value. 

478 

479 Format Definition: 

480 This value type is defined by the following notation: 

481 

482 .. code-block:: text 

483 

484 integer = (["+"] / "-") 1*DIGIT 

485 

486 Description: 

487 If the property permits, multiple "integer" values are 

488 specified by a COMMA-separated list of values. The valid range 

489 for "integer" is -2147483648 to 2147483647. If the sign is not 

490 specified, then the value is assumed to be positive. 

491 

492 Example: 

493 

494 .. code-block:: text 

495 

496 1234567890 

497 -1234567890 

498 +1234567890 

499 432109876 

500 

501 .. code-block:: pycon 

502 

503 >>> from icalendar.prop import vInt 

504 >>> integer = vInt.from_ical('1234567890') 

505 >>> integer 

506 1234567890 

507 >>> integer = vInt.from_ical('-1234567890') 

508 >>> integer 

509 -1234567890 

510 >>> integer = vInt.from_ical('+1234567890') 

511 >>> integer 

512 1234567890 

513 >>> integer = vInt.from_ical('432109876') 

514 >>> integer 

515 432109876 

516 """ 

517 

518 params: Parameters 

519 

520 def __new__(cls, *args, params: dict[str, Any] | None = None, **kwargs): 

521 if params is None: 

522 params = {} 

523 self = super().__new__(cls, *args, **kwargs) 

524 self.params = Parameters(params) 

525 return self 

526 

527 def to_ical(self) -> bytes: 

528 return str(self).encode("utf-8") 

529 

530 @classmethod 

531 def from_ical(cls, ical: ICAL_TYPE): 

532 try: 

533 return cls(ical) 

534 except Exception as e: 

535 raise ValueError(f"Expected int, got: {ical}") from e 

536 

537 

538class vDDDLists: 

539 """A list of vDDDTypes values.""" 

540 

541 params: Parameters 

542 dts: list 

543 

544 def __init__(self, dt_list): 

545 if not hasattr(dt_list, "__iter__"): 

546 dt_list = [dt_list] 

547 vddd = [] 

548 tzid = None 

549 for dt_l in dt_list: 

550 dt = vDDDTypes(dt_l) 

551 vddd.append(dt) 

552 if "TZID" in dt.params: 

553 tzid = dt.params["TZID"] 

554 

555 params = {} 

556 if tzid: 

557 # NOTE: no support for multiple timezones here! 

558 params["TZID"] = tzid 

559 self.params = Parameters(params) 

560 self.dts = vddd 

561 

562 def to_ical(self): 

563 dts_ical = (from_unicode(dt.to_ical()) for dt in self.dts) 

564 return b",".join(dts_ical) 

565 

566 @staticmethod 

567 def from_ical(ical, timezone=None): 

568 out = [] 

569 ical_dates = ical.split(",") 

570 for ical_dt in ical_dates: 

571 out.append(vDDDTypes.from_ical(ical_dt, timezone=timezone)) 

572 return out 

573 

574 def __eq__(self, other): 

575 if isinstance(other, vDDDLists): 

576 return self.dts == other.dts 

577 if isinstance(other, (TimeBase, date)): 

578 return self.dts == [other] 

579 return False 

580 

581 def __repr__(self): 

582 """String representation.""" 

583 return f"{self.__class__.__name__}({self.dts})" 

584 

585 

586class vCategory: 

587 params: Parameters 

588 

589 def __init__( 

590 self, c_list: list[str] | str, /, params: dict[str, Any] | None = None 

591 ): 

592 if params is None: 

593 params = {} 

594 if not hasattr(c_list, "__iter__") or isinstance(c_list, str): 

595 c_list = [c_list] 

596 self.cats: list[vText | str] = [vText(c) for c in c_list] 

597 self.params = Parameters(params) 

598 

599 def __iter__(self): 

600 return iter(vCategory.from_ical(self.to_ical())) 

601 

602 def to_ical(self): 

603 return b",".join( 

604 [ 

605 c.to_ical() if hasattr(c, "to_ical") else vText(c).to_ical() 

606 for c in self.cats 

607 ] 

608 ) 

609 

610 @staticmethod 

611 def from_ical(ical): 

612 ical = to_unicode(ical) 

613 return unescape_char(ical).split(",") 

614 

615 def __eq__(self, other): 

616 """self == other""" 

617 return isinstance(other, vCategory) and self.cats == other.cats 

618 

619 def __repr__(self): 

620 """String representation.""" 

621 return f"{self.__class__.__name__}({self.cats}, params={self.params})" 

622 

623 

624class TimeBase: 

625 """Make classes with a datetime/date comparable.""" 

626 

627 params: Parameters 

628 ignore_for_equality = {"TZID", "VALUE"} 

629 

630 def __eq__(self, other): 

631 """self == other""" 

632 if isinstance(other, date): 

633 return self.dt == other 

634 if isinstance(other, TimeBase): 

635 default = object() 

636 for key in ( 

637 set(self.params) | set(other.params) 

638 ) - self.ignore_for_equality: 

639 if key[:2].lower() != "x-" and self.params.get( 

640 key, default 

641 ) != other.params.get(key, default): 

642 return False 

643 return self.dt == other.dt 

644 if isinstance(other, vDDDLists): 

645 return other == self 

646 return False 

647 

648 def __hash__(self): 

649 return hash(self.dt) 

650 

651 from icalendar.param import RANGE, RELATED, TZID 

652 

653 def __repr__(self): 

654 """String representation.""" 

655 return f"{self.__class__.__name__}({self.dt}, {self.params})" 

656 

657 

658class vDDDTypes(TimeBase): 

659 """A combined Datetime, Date or Duration parser/generator. Their format 

660 cannot be confused, and often values can be of either types. 

661 So this is practical. 

662 """ 

663 

664 params: Parameters 

665 

666 def __init__(self, dt): 

667 if not isinstance(dt, (datetime, date, timedelta, time, tuple)): 

668 raise TypeError( 

669 "You must use datetime, date, timedelta, time or tuple (for periods)" 

670 ) 

671 if isinstance(dt, (datetime, timedelta)): 

672 self.params = Parameters() 

673 elif isinstance(dt, date): 

674 self.params = Parameters({"value": "DATE"}) 

675 elif isinstance(dt, time): 

676 self.params = Parameters({"value": "TIME"}) 

677 else: # isinstance(dt, tuple) 

678 self.params = Parameters({"value": "PERIOD"}) 

679 

680 tzid = tzid_from_dt(dt) if isinstance(dt, (datetime, time)) else None 

681 if tzid is not None and tzid != "UTC": 

682 self.params.update({"TZID": tzid}) 

683 

684 self.dt = dt 

685 

686 def to_ical(self): 

687 dt = self.dt 

688 if isinstance(dt, datetime): 

689 return vDatetime(dt).to_ical() 

690 if isinstance(dt, date): 

691 return vDate(dt).to_ical() 

692 if isinstance(dt, timedelta): 

693 return vDuration(dt).to_ical() 

694 if isinstance(dt, time): 

695 return vTime(dt).to_ical() 

696 if isinstance(dt, tuple) and len(dt) == 2: 

697 return vPeriod(dt).to_ical() 

698 raise ValueError(f"Unknown date type: {type(dt)}") 

699 

700 @classmethod 

701 def from_ical(cls, ical, timezone=None): 

702 if isinstance(ical, cls): 

703 return ical.dt 

704 u = ical.upper() 

705 if u.startswith(("P", "-P", "+P")): 

706 return vDuration.from_ical(ical) 

707 if "/" in u: 

708 return vPeriod.from_ical(ical, timezone=timezone) 

709 

710 if len(ical) in (15, 16): 

711 return vDatetime.from_ical(ical, timezone=timezone) 

712 if len(ical) == 8: 

713 if timezone: 

714 tzinfo = tzp.timezone(timezone) 

715 if tzinfo is not None: 

716 return to_datetime(vDate.from_ical(ical)).replace(tzinfo=tzinfo) 

717 return vDate.from_ical(ical) 

718 if len(ical) in (6, 7): 

719 return vTime.from_ical(ical) 

720 raise ValueError(f"Expected datetime, date, or time. Got: '{ical}'") 

721 

722 @property 

723 def td(self) -> timedelta: 

724 """Compatibility property returning ``self.dt``. 

725 

726 This class is used to replace different time components. 

727 Some of them contain a datetime or date (``.dt``). 

728 Some of them contain a timedelta (``.td``). 

729 This property allows interoperability. 

730 """ 

731 return self.dt 

732 

733 

734class vDate(TimeBase): 

735 """Date 

736 

737 Value Name: 

738 DATE 

739 

740 Purpose: 

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

742 calendar date. 

743 

744 Format Definition: 

745 This value type is defined by the following notation: 

746 

747 .. code-block:: text 

748 

749 date = date-value 

750 

751 date-value = date-fullyear date-month date-mday 

752 date-fullyear = 4DIGIT 

753 date-month = 2DIGIT ;01-12 

754 date-mday = 2DIGIT ;01-28, 01-29, 01-30, 01-31 

755 ;based on month/year 

756 

757 Description: 

758 If the property permits, multiple "date" values are 

759 specified as a COMMA-separated list of values. The format for the 

760 value type is based on the [ISO.8601.2004] complete 

761 representation, basic format for a calendar date. The textual 

762 format specifies a four-digit year, two-digit month, and two-digit 

763 day of the month. There are no separator characters between the 

764 year, month, and day component text. 

765 

766 Example: 

767 The following represents July 14, 1997: 

768 

769 .. code-block:: text 

770 

771 19970714 

772 

773 .. code-block:: pycon 

774 

775 >>> from icalendar.prop import vDate 

776 >>> date = vDate.from_ical('19970714') 

777 >>> date.year 

778 1997 

779 >>> date.month 

780 7 

781 >>> date.day 

782 14 

783 """ 

784 

785 params: Parameters 

786 

787 def __init__(self, dt): 

788 if not isinstance(dt, date): 

789 raise TypeError("Value MUST be a date instance") 

790 self.dt = dt 

791 self.params = Parameters({"value": "DATE"}) 

792 

793 def to_ical(self): 

794 s = f"{self.dt.year:04}{self.dt.month:02}{self.dt.day:02}" 

795 return s.encode("utf-8") 

796 

797 @staticmethod 

798 def from_ical(ical): 

799 try: 

800 timetuple = ( 

801 int(ical[:4]), # year 

802 int(ical[4:6]), # month 

803 int(ical[6:8]), # day 

804 ) 

805 return date(*timetuple) 

806 except Exception as e: 

807 raise ValueError(f"Wrong date format {ical}") from e 

808 

809 

810class vDatetime(TimeBase): 

811 """Date-Time 

812 

813 Value Name: DATE-TIME 

814 

815 Purpose: This value type is used to identify values that specify a 

816 precise calendar date and time of day. The format is based on 

817 the ISO.8601.2004 complete representation. 

818 

819 Format: 

820 YYYYMMDDTHHMMSS 

821 

822 Descripiton: vDatetime is timezone aware and uses a timezone library. 

823 When a vDatetime object is created from an 

824 ical string, you can pass a valid timezone identifier. When a 

825 vDatetime object is created from a python datetime object, it uses the 

826 tzinfo component, if present. Otherwise a timezone-naive object is 

827 created. Be aware that there are certain limitations with timezone naive 

828 DATE-TIME components in the icalendar standard. 

829 

830 Example 1: The following represents March 2, 2021 at 10:15 with local time: 

831 

832 .. code-block:: pycon 

833 

834 >>> from icalendar import vDatetime 

835 >>> datetime = vDatetime.from_ical("20210302T101500") 

836 >>> datetime.tzname() 

837 >>> datetime.year 

838 2021 

839 >>> datetime.minute 

840 15 

841 

842 Example 2: The following represents March 2, 2021 at 10:15 in New York: 

843 

844 .. code-block:: pycon 

845 

846 >>> datetime = vDatetime.from_ical("20210302T101500", 'America/New_York') 

847 >>> datetime.tzname() 

848 'EST' 

849 

850 Example 3: The following represents March 2, 2021 at 10:15 in Berlin: 

851 

852 .. code-block:: pycon 

853 

854 >>> from zoneinfo import ZoneInfo 

855 >>> timezone = ZoneInfo("Europe/Berlin") 

856 >>> vDatetime.from_ical("20210302T101500", timezone) 

857 datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='Europe/Berlin')) 

858 """ 

859 

860 params: Parameters 

861 

862 def __init__(self, dt, /, params: dict[str, Any] | None = None): 

863 if params is None: 

864 params = {} 

865 self.dt = dt 

866 self.params = Parameters(params) 

867 

868 def to_ical(self): 

869 dt = self.dt 

870 tzid = tzid_from_dt(dt) 

871 

872 s = ( 

873 f"{dt.year:04}{dt.month:02}{dt.day:02}" 

874 f"T{dt.hour:02}{dt.minute:02}{dt.second:02}" 

875 ) 

876 if tzid == "UTC": 

877 s += "Z" 

878 elif tzid: 

879 self.params.update({"TZID": tzid}) 

880 return s.encode("utf-8") 

881 

882 @staticmethod 

883 def from_ical(ical, timezone=None): 

884 """Create a datetime from the RFC string.""" 

885 tzinfo = None 

886 if isinstance(timezone, str): 

887 tzinfo = tzp.timezone(timezone) 

888 elif timezone is not None: 

889 tzinfo = timezone 

890 

891 try: 

892 timetuple = ( 

893 int(ical[:4]), # year 

894 int(ical[4:6]), # month 

895 int(ical[6:8]), # day 

896 int(ical[9:11]), # hour 

897 int(ical[11:13]), # minute 

898 int(ical[13:15]), # second 

899 ) 

900 if tzinfo: 

901 return tzp.localize(datetime(*timetuple), tzinfo) # noqa: DTZ001 

902 if not ical[15:]: 

903 return datetime(*timetuple) # noqa: DTZ001 

904 if ical[15:16] == "Z": 

905 return tzp.localize_utc(datetime(*timetuple)) # noqa: DTZ001 

906 except Exception as e: 

907 raise ValueError(f"Wrong datetime format: {ical}") from e 

908 raise ValueError(f"Wrong datetime format: {ical}") 

909 

910 

911class vDuration(TimeBase): 

912 """Duration 

913 

914 Value Name: 

915 DURATION 

916 

917 Purpose: 

918 This value type is used to identify properties that contain 

919 a duration of time. 

920 

921 Format Definition: 

922 This value type is defined by the following notation: 

923 

924 .. code-block:: text 

925 

926 dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week) 

927 

928 dur-date = dur-day [dur-time] 

929 dur-time = "T" (dur-hour / dur-minute / dur-second) 

930 dur-week = 1*DIGIT "W" 

931 dur-hour = 1*DIGIT "H" [dur-minute] 

932 dur-minute = 1*DIGIT "M" [dur-second] 

933 dur-second = 1*DIGIT "S" 

934 dur-day = 1*DIGIT "D" 

935 

936 Description: 

937 If the property permits, multiple "duration" values are 

938 specified by a COMMA-separated list of values. The format is 

939 based on the [ISO.8601.2004] complete representation basic format 

940 with designators for the duration of time. The format can 

941 represent nominal durations (weeks and days) and accurate 

942 durations (hours, minutes, and seconds). Note that unlike 

943 [ISO.8601.2004], this value type doesn't support the "Y" and "M" 

944 designators to specify durations in terms of years and months. 

945 The duration of a week or a day depends on its position in the 

946 calendar. In the case of discontinuities in the time scale, such 

947 as the change from standard time to daylight time and back, the 

948 computation of the exact duration requires the subtraction or 

949 addition of the change of duration of the discontinuity. Leap 

950 seconds MUST NOT be considered when computing an exact duration. 

951 When computing an exact duration, the greatest order time 

952 components MUST be added first, that is, the number of days MUST 

953 be added first, followed by the number of hours, number of 

954 minutes, and number of seconds. 

955 

956 Example: 

957 A duration of 15 days, 5 hours, and 20 seconds would be: 

958 

959 .. code-block:: text 

960 

961 P15DT5H0M20S 

962 

963 A duration of 7 weeks would be: 

964 

965 .. code-block:: text 

966 

967 P7W 

968 

969 .. code-block:: pycon 

970 

971 >>> from icalendar.prop import vDuration 

972 >>> duration = vDuration.from_ical('P15DT5H0M20S') 

973 >>> duration 

974 datetime.timedelta(days=15, seconds=18020) 

975 >>> duration = vDuration.from_ical('P7W') 

976 >>> duration 

977 datetime.timedelta(days=49) 

978 """ 

979 

980 params: Parameters 

981 

982 def __init__(self, td, /, params: dict[str, Any] | None = None): 

983 if params is None: 

984 params = {} 

985 if not isinstance(td, timedelta): 

986 raise TypeError("Value MUST be a timedelta instance") 

987 self.td = td 

988 self.params = Parameters(params) 

989 

990 def to_ical(self): 

991 sign = "" 

992 td = self.td 

993 if td.days < 0: 

994 sign = "-" 

995 td = -td 

996 timepart = "" 

997 if td.seconds: 

998 timepart = "T" 

999 hours = td.seconds // 3600 

1000 minutes = td.seconds % 3600 // 60 

1001 seconds = td.seconds % 60 

1002 if hours: 

1003 timepart += f"{hours}H" 

1004 if minutes or (hours and seconds): 

1005 timepart += f"{minutes}M" 

1006 if seconds: 

1007 timepart += f"{seconds}S" 

1008 if td.days == 0 and timepart: 

1009 return str(sign).encode("utf-8") + b"P" + str(timepart).encode("utf-8") 

1010 return ( 

1011 str(sign).encode("utf-8") 

1012 + b"P" 

1013 + str(abs(td.days)).encode("utf-8") 

1014 + b"D" 

1015 + str(timepart).encode("utf-8") 

1016 ) 

1017 

1018 @staticmethod 

1019 def from_ical(ical): 

1020 match = DURATION_REGEX.match(ical) 

1021 if not match: 

1022 raise ValueError(f"Invalid iCalendar duration: {ical}") 

1023 

1024 sign, weeks, days, hours, minutes, seconds = match.groups() 

1025 value = timedelta( 

1026 weeks=int(weeks or 0), 

1027 days=int(days or 0), 

1028 hours=int(hours or 0), 

1029 minutes=int(minutes or 0), 

1030 seconds=int(seconds or 0), 

1031 ) 

1032 

1033 if sign == "-": 

1034 value = -value 

1035 

1036 return value 

1037 

1038 @property 

1039 def dt(self) -> timedelta: 

1040 """The time delta for compatibility.""" 

1041 return self.td 

1042 

1043 

1044class vPeriod(TimeBase): 

1045 """Period of Time 

1046 

1047 Value Name: 

1048 PERIOD 

1049 

1050 Purpose: 

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

1052 precise period of time. 

1053 

1054 Format Definition: 

1055 This value type is defined by the following notation: 

1056 

1057 .. code-block:: text 

1058 

1059 period = period-explicit / period-start 

1060 

1061 period-explicit = date-time "/" date-time 

1062 ; [ISO.8601.2004] complete representation basic format for a 

1063 ; period of time consisting of a start and end. The start MUST 

1064 ; be before the end. 

1065 

1066 period-start = date-time "/" dur-value 

1067 ; [ISO.8601.2004] complete representation basic format for a 

1068 ; period of time consisting of a start and positive duration 

1069 ; of time. 

1070 

1071 Description: 

1072 If the property permits, multiple "period" values are 

1073 specified by a COMMA-separated list of values. There are two 

1074 forms of a period of time. First, a period of time is identified 

1075 by its start and its end. This format is based on the 

1076 [ISO.8601.2004] complete representation, basic format for "DATE- 

1077 TIME" start of the period, followed by a SOLIDUS character 

1078 followed by the "DATE-TIME" of the end of the period. The start 

1079 of the period MUST be before the end of the period. Second, a 

1080 period of time can also be defined by a start and a positive 

1081 duration of time. The format is based on the [ISO.8601.2004] 

1082 complete representation, basic format for the "DATE-TIME" start of 

1083 the period, followed by a SOLIDUS character, followed by the 

1084 [ISO.8601.2004] basic format for "DURATION" of the period. 

1085 

1086 Example: 

1087 The period starting at 18:00:00 UTC, on January 1, 1997 and 

1088 ending at 07:00:00 UTC on January 2, 1997 would be: 

1089 

1090 .. code-block:: text 

1091 

1092 19970101T180000Z/19970102T070000Z 

1093 

1094 The period start at 18:00:00 on January 1, 1997 and lasting 5 hours 

1095 and 30 minutes would be: 

1096 

1097 .. code-block:: text 

1098 

1099 19970101T180000Z/PT5H30M 

1100 

1101 .. code-block:: pycon 

1102 

1103 >>> from icalendar.prop import vPeriod 

1104 >>> period = vPeriod.from_ical('19970101T180000Z/19970102T070000Z') 

1105 >>> period = vPeriod.from_ical('19970101T180000Z/PT5H30M') 

1106 """ 

1107 

1108 params: Parameters 

1109 

1110 def __init__(self, per: tuple[datetime, Union[datetime, timedelta]]): 

1111 start, end_or_duration = per 

1112 if not (isinstance(start, (datetime, date))): 

1113 raise TypeError("Start value MUST be a datetime or date instance") 

1114 if not (isinstance(end_or_duration, (datetime, date, timedelta))): 

1115 raise TypeError( 

1116 "end_or_duration MUST be a datetime, date or timedelta instance" 

1117 ) 

1118 by_duration = 0 

1119 if isinstance(end_or_duration, timedelta): 

1120 by_duration = 1 

1121 duration = end_or_duration 

1122 end = start + duration 

1123 else: 

1124 end = end_or_duration 

1125 duration = end - start 

1126 if start > end: 

1127 raise ValueError("Start time is greater than end time") 

1128 

1129 self.params = Parameters({"value": "PERIOD"}) 

1130 # set the timezone identifier 

1131 # does not support different timezones for start and end 

1132 tzid = tzid_from_dt(start) 

1133 if tzid: 

1134 self.params["TZID"] = tzid 

1135 

1136 self.start = start 

1137 self.end = end 

1138 self.by_duration = by_duration 

1139 self.duration = duration 

1140 

1141 def overlaps(self, other): 

1142 if self.start > other.start: 

1143 return other.overlaps(self) 

1144 return self.start <= other.start < self.end 

1145 

1146 def to_ical(self): 

1147 if self.by_duration: 

1148 return ( 

1149 vDatetime(self.start).to_ical() 

1150 + b"/" 

1151 + vDuration(self.duration).to_ical() 

1152 ) 

1153 return vDatetime(self.start).to_ical() + b"/" + vDatetime(self.end).to_ical() 

1154 

1155 @staticmethod 

1156 def from_ical(ical, timezone=None): 

1157 try: 

1158 start, end_or_duration = ical.split("/") 

1159 start = vDDDTypes.from_ical(start, timezone=timezone) 

1160 end_or_duration = vDDDTypes.from_ical(end_or_duration, timezone=timezone) 

1161 except Exception as e: 

1162 raise ValueError(f"Expected period format, got: {ical}") from e 

1163 return (start, end_or_duration) 

1164 

1165 def __repr__(self): 

1166 p = (self.start, self.duration) if self.by_duration else (self.start, self.end) 

1167 return f"vPeriod({p!r})" 

1168 

1169 @property 

1170 def dt(self): 

1171 """Make this cooperate with the other vDDDTypes.""" 

1172 return (self.start, (self.duration if self.by_duration else self.end)) 

1173 

1174 from icalendar.param import FBTYPE 

1175 

1176 

1177class vWeekday(str): 

1178 """Either a ``weekday`` or a ``weekdaynum`` 

1179 

1180 .. code-block:: pycon 

1181 

1182 >>> from icalendar import vWeekday 

1183 >>> vWeekday("MO") # Simple weekday 

1184 'MO' 

1185 >>> vWeekday("2FR").relative # Second friday 

1186 2 

1187 >>> vWeekday("2FR").weekday 

1188 'FR' 

1189 >>> vWeekday("-1SU").relative # Last Sunday 

1190 -1 

1191 

1192 Definition from `RFC 5545, Section 3.3.10 <https://www.rfc-editor.org/rfc/rfc5545#section-3.3.10>`_: 

1193 

1194 .. code-block:: text 

1195 

1196 weekdaynum = [[plus / minus] ordwk] weekday 

1197 plus = "+" 

1198 minus = "-" 

1199 ordwk = 1*2DIGIT ;1 to 53 

1200 weekday = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA" 

1201 ;Corresponding to SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, 

1202 ;FRIDAY, and SATURDAY days of the week. 

1203 

1204 """ 

1205 

1206 params: Parameters 

1207 __slots__ = ("params", "relative", "weekday") 

1208 

1209 week_days = CaselessDict( 

1210 { 

1211 "SU": 0, 

1212 "MO": 1, 

1213 "TU": 2, 

1214 "WE": 3, 

1215 "TH": 4, 

1216 "FR": 5, 

1217 "SA": 6, 

1218 } 

1219 ) 

1220 

1221 def __new__( 

1222 cls, 

1223 value, 

1224 encoding=DEFAULT_ENCODING, 

1225 /, 

1226 params: dict[str, Any] | None = None, 

1227 ): 

1228 if params is None: 

1229 params = {} 

1230 value = to_unicode(value, encoding=encoding) 

1231 self = super().__new__(cls, value) 

1232 match = WEEKDAY_RULE.match(self) 

1233 if match is None: 

1234 raise ValueError(f"Expected weekday abbrevation, got: {self}") 

1235 match = match.groupdict() 

1236 sign = match["signal"] 

1237 weekday = match["weekday"] 

1238 relative = match["relative"] 

1239 if weekday not in vWeekday.week_days or sign not in "+-": 

1240 raise ValueError(f"Expected weekday abbrevation, got: {self}") 

1241 self.weekday = weekday or None 

1242 self.relative = (relative and int(relative)) or None 

1243 if sign == "-" and self.relative: 

1244 self.relative *= -1 

1245 self.params = Parameters(params) 

1246 return self 

1247 

1248 def to_ical(self): 

1249 return self.encode(DEFAULT_ENCODING).upper() 

1250 

1251 @classmethod 

1252 def from_ical(cls, ical): 

1253 try: 

1254 return cls(ical.upper()) 

1255 except Exception as e: 

1256 raise ValueError(f"Expected weekday abbrevation, got: {ical}") from e 

1257 

1258 

1259class vFrequency(str): 

1260 """A simple class that catches illegal values.""" 

1261 

1262 params: Parameters 

1263 __slots__ = ("params",) 

1264 

1265 frequencies = CaselessDict( 

1266 { 

1267 "SECONDLY": "SECONDLY", 

1268 "MINUTELY": "MINUTELY", 

1269 "HOURLY": "HOURLY", 

1270 "DAILY": "DAILY", 

1271 "WEEKLY": "WEEKLY", 

1272 "MONTHLY": "MONTHLY", 

1273 "YEARLY": "YEARLY", 

1274 } 

1275 ) 

1276 

1277 def __new__( 

1278 cls, 

1279 value, 

1280 encoding=DEFAULT_ENCODING, 

1281 /, 

1282 params: dict[str, Any] | None = None, 

1283 ): 

1284 if params is None: 

1285 params = {} 

1286 value = to_unicode(value, encoding=encoding) 

1287 self = super().__new__(cls, value) 

1288 if self not in vFrequency.frequencies: 

1289 raise ValueError(f"Expected frequency, got: {self}") 

1290 self.params = Parameters(params) 

1291 return self 

1292 

1293 def to_ical(self): 

1294 return self.encode(DEFAULT_ENCODING).upper() 

1295 

1296 @classmethod 

1297 def from_ical(cls, ical): 

1298 try: 

1299 return cls(ical.upper()) 

1300 except Exception as e: 

1301 raise ValueError(f"Expected frequency, got: {ical}") from e 

1302 

1303 

1304class vMonth(int): 

1305 """The number of the month for recurrence. 

1306 

1307 In :rfc:`5545`, this is just an int. 

1308 In :rfc:`7529`, this can be followed by `L` to indicate a leap month. 

1309 

1310 .. code-block:: pycon 

1311 

1312 >>> from icalendar import vMonth 

1313 >>> vMonth(1) # first month January 

1314 vMonth('1') 

1315 >>> vMonth("5L") # leap month in Hebrew calendar 

1316 vMonth('5L') 

1317 >>> vMonth(1).leap 

1318 False 

1319 >>> vMonth("5L").leap 

1320 True 

1321 

1322 Definition from RFC: 

1323 

1324 .. code-block:: text 

1325 

1326 type-bymonth = element bymonth { 

1327 xsd:positiveInteger | 

1328 xsd:string 

1329 } 

1330 """ 

1331 

1332 params: Parameters 

1333 

1334 def __new__( 

1335 cls, month: Union[str, int], /, params: dict[str, Any] | None = None 

1336 ): 

1337 if params is None: 

1338 params = {} 

1339 if isinstance(month, vMonth): 

1340 return cls(month.to_ical().decode()) 

1341 if isinstance(month, str): 

1342 if month.isdigit(): 

1343 month_index = int(month) 

1344 leap = False 

1345 else: 

1346 if month[-1] != "L" and month[:-1].isdigit(): 

1347 raise ValueError(f"Invalid month: {month!r}") 

1348 month_index = int(month[:-1]) 

1349 leap = True 

1350 else: 

1351 leap = False 

1352 month_index = int(month) 

1353 self = super().__new__(cls, month_index) 

1354 self.leap = leap 

1355 self.params = Parameters(params) 

1356 return self 

1357 

1358 def to_ical(self) -> bytes: 

1359 """The ical representation.""" 

1360 return str(self).encode("utf-8") 

1361 

1362 @classmethod 

1363 def from_ical(cls, ical: str): 

1364 return cls(ical) 

1365 

1366 @property 

1367 def leap(self) -> bool: 

1368 """Whether this is a leap month.""" 

1369 return self._leap 

1370 

1371 @leap.setter 

1372 def leap(self, value: bool) -> None: 

1373 self._leap = value 

1374 

1375 def __repr__(self) -> str: 

1376 """repr(self)""" 

1377 return f"{self.__class__.__name__}({str(self)!r})" 

1378 

1379 def __str__(self) -> str: 

1380 """str(self)""" 

1381 return f"{int(self)}{'L' if self.leap else ''}" 

1382 

1383 

1384class vSkip(vText, Enum): 

1385 """Skip values for RRULE. 

1386 

1387 These are defined in :rfc:`7529`. 

1388 

1389 OMIT is the default value. 

1390 

1391 Examples: 

1392 

1393 .. code-block:: pycon 

1394 

1395 >>> from icalendar import vSkip 

1396 >>> vSkip.OMIT 

1397 vSkip('OMIT') 

1398 >>> vSkip.FORWARD 

1399 vSkip('FORWARD') 

1400 >>> vSkip.BACKWARD 

1401 vSkip('BACKWARD') 

1402 """ 

1403 

1404 OMIT = "OMIT" 

1405 FORWARD = "FORWARD" 

1406 BACKWARD = "BACKWARD" 

1407 

1408 __reduce_ex__ = Enum.__reduce_ex__ 

1409 

1410 def __repr__(self): 

1411 return f"{self.__class__.__name__}({self._name_!r})" 

1412 

1413 

1414class vRecur(CaselessDict): 

1415 """Recurrence definition. 

1416 

1417 Property Name: 

1418 RRULE 

1419 

1420 Purpose: 

1421 This property defines a rule or repeating pattern for recurring events, to-dos, 

1422 journal entries, or time zone definitions. 

1423 

1424 Value Type: 

1425 RECUR 

1426 

1427 Property Parameters: 

1428 IANA and non-standard property parameters can be specified on this property. 

1429 

1430 Conformance: 

1431 This property can be specified in recurring "VEVENT", "VTODO", and "VJOURNAL" 

1432 calendar components as well as in the "STANDARD" and "DAYLIGHT" sub-components 

1433 of the "VTIMEZONE" calendar component, but it SHOULD NOT be specified more than once. 

1434 The recurrence set generated with multiple "RRULE" properties is undefined. 

1435 

1436 Description: 

1437 The recurrence rule, if specified, is used in computing the recurrence set. 

1438 The recurrence set is the complete set of recurrence instances for a calendar component. 

1439 The recurrence set is generated by considering the initial "DTSTART" property along 

1440 with the "RRULE", "RDATE", and "EXDATE" properties contained within the 

1441 recurring component. The "DTSTART" property defines the first instance in the 

1442 recurrence set. The "DTSTART" property value SHOULD be synchronized with the 

1443 recurrence rule, if specified. The recurrence set generated with a "DTSTART" property 

1444 value not synchronized with the recurrence rule is undefined. 

1445 The final recurrence set is generated by gathering all of the start DATE-TIME 

1446 values generated by any of the specified "RRULE" and "RDATE" properties, and then 

1447 excluding any start DATE-TIME values specified by "EXDATE" properties. 

1448 This implies that start DATE- TIME values specified by "EXDATE" properties take 

1449 precedence over those specified by inclusion properties (i.e., "RDATE" and "RRULE"). 

1450 Where duplicate instances are generated by the "RRULE" and "RDATE" properties, 

1451 only one recurrence is considered. Duplicate instances are ignored. 

1452 

1453 The "DTSTART" property specified within the iCalendar object defines the first 

1454 instance of the recurrence. In most cases, a "DTSTART" property of DATE-TIME value 

1455 type used with a recurrence rule, should be specified as a date with local time 

1456 and time zone reference to make sure all the recurrence instances start at the 

1457 same local time regardless of time zone changes. 

1458 

1459 If the duration of the recurring component is specified with the "DTEND" or 

1460 "DUE" property, then the same exact duration will apply to all the members of the 

1461 generated recurrence set. Else, if the duration of the recurring component is 

1462 specified with the "DURATION" property, then the same nominal duration will apply 

1463 to all the members of the generated recurrence set and the exact duration of each 

1464 recurrence instance will depend on its specific start time. For example, recurrence 

1465 instances of a nominal duration of one day will have an exact duration of more or less 

1466 than 24 hours on a day where a time zone shift occurs. The duration of a specific 

1467 recurrence may be modified in an exception component or simply by using an 

1468 "RDATE" property of PERIOD value type. 

1469 

1470 Examples: 

1471 The following RRULE specifies daily events for 10 occurrences. 

1472 

1473 .. code-block:: text 

1474 

1475 RRULE:FREQ=DAILY;COUNT=10 

1476 

1477 Below, we parse the RRULE ical string. 

1478 

1479 .. code-block:: pycon 

1480 

1481 >>> from icalendar.prop import vRecur 

1482 >>> rrule = vRecur.from_ical('FREQ=DAILY;COUNT=10') 

1483 >>> rrule 

1484 vRecur({'FREQ': ['DAILY'], 'COUNT': [10]}) 

1485 

1486 You can choose to add an rrule to an :class:`icalendar.cal.Event` or 

1487 :class:`icalendar.cal.Todo`. 

1488 

1489 .. code-block:: pycon 

1490 

1491 >>> from icalendar import Event 

1492 >>> event = Event() 

1493 >>> event.add('RRULE', 'FREQ=DAILY;COUNT=10') 

1494 >>> event.rrules 

1495 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})] 

1496 """ # noqa: E501 

1497 

1498 params: Parameters 

1499 

1500 frequencies = [ 

1501 "SECONDLY", 

1502 "MINUTELY", 

1503 "HOURLY", 

1504 "DAILY", 

1505 "WEEKLY", 

1506 "MONTHLY", 

1507 "YEARLY", 

1508 ] 

1509 

1510 # Mac iCal ignores RRULEs where FREQ is not the first rule part. 

1511 # Sorts parts according to the order listed in RFC 5545, section 3.3.10. 

1512 canonical_order = ( 

1513 "RSCALE", 

1514 "FREQ", 

1515 "UNTIL", 

1516 "COUNT", 

1517 "INTERVAL", 

1518 "BYSECOND", 

1519 "BYMINUTE", 

1520 "BYHOUR", 

1521 "BYDAY", 

1522 "BYWEEKDAY", 

1523 "BYMONTHDAY", 

1524 "BYYEARDAY", 

1525 "BYWEEKNO", 

1526 "BYMONTH", 

1527 "BYSETPOS", 

1528 "WKST", 

1529 "SKIP", 

1530 ) 

1531 

1532 types = CaselessDict( 

1533 { 

1534 "COUNT": vInt, 

1535 "INTERVAL": vInt, 

1536 "BYSECOND": vInt, 

1537 "BYMINUTE": vInt, 

1538 "BYHOUR": vInt, 

1539 "BYWEEKNO": vInt, 

1540 "BYMONTHDAY": vInt, 

1541 "BYYEARDAY": vInt, 

1542 "BYMONTH": vMonth, 

1543 "UNTIL": vDDDTypes, 

1544 "BYSETPOS": vInt, 

1545 "WKST": vWeekday, 

1546 "BYDAY": vWeekday, 

1547 "FREQ": vFrequency, 

1548 "BYWEEKDAY": vWeekday, 

1549 "SKIP": vSkip, 

1550 } 

1551 ) 

1552 

1553 def __init__(self, *args, params: dict[str, Any] | None = None, **kwargs): 

1554 if params is None: 

1555 params = {} 

1556 if args and isinstance(args[0], str): 

1557 # we have a string as an argument. 

1558 args = (self.from_ical(args[0]),) + args[1:] 

1559 for k, v in kwargs.items(): 

1560 if not isinstance(v, SEQUENCE_TYPES): 

1561 kwargs[k] = [v] 

1562 super().__init__(*args, **kwargs) 

1563 self.params = Parameters(params) 

1564 

1565 def to_ical(self): 

1566 result = [] 

1567 for key, vals in self.sorted_items(): 

1568 typ = self.types.get(key, vText) 

1569 if not isinstance(vals, SEQUENCE_TYPES): 

1570 vals = [vals] # noqa: PLW2901 

1571 param_vals = b",".join(typ(val).to_ical() for val in vals) 

1572 

1573 # CaselessDict keys are always unicode 

1574 param_key = key.encode(DEFAULT_ENCODING) 

1575 result.append(param_key + b"=" + param_vals) 

1576 

1577 return b";".join(result) 

1578 

1579 @classmethod 

1580 def parse_type(cls, key, values): 

1581 # integers 

1582 parser = cls.types.get(key, vText) 

1583 return [parser.from_ical(v) for v in values.split(",")] 

1584 

1585 @classmethod 

1586 def from_ical(cls, ical: str): 

1587 if isinstance(ical, cls): 

1588 return ical 

1589 try: 

1590 recur = cls() 

1591 for pairs in ical.split(";"): 

1592 try: 

1593 key, vals = pairs.split("=") 

1594 except ValueError: 

1595 # E.g. incorrect trailing semicolon, like (issue #157): 

1596 # FREQ=YEARLY;BYMONTH=11;BYDAY=1SU; 

1597 continue 

1598 recur[key] = cls.parse_type(key, vals) 

1599 return cls(recur) 

1600 except ValueError: 

1601 raise 

1602 except Exception as e: 

1603 raise ValueError(f"Error in recurrence rule: {ical}") from e 

1604 

1605 

1606class vTime(TimeBase): 

1607 """Time 

1608 

1609 Value Name: 

1610 TIME 

1611 

1612 Purpose: 

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

1614 time of day. 

1615 

1616 Format Definition: 

1617 This value type is defined by the following notation: 

1618 

1619 .. code-block:: text 

1620 

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

1622 

1623 time-hour = 2DIGIT ;00-23 

1624 time-minute = 2DIGIT ;00-59 

1625 time-second = 2DIGIT ;00-60 

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

1627 

1628 time-utc = "Z" 

1629 

1630 Description: 

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

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

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

1634 vText) is defined for this value type. 

1635 

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

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

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

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

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

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

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

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

1644 

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

1646 type expresses time values in three forms: 

1647 

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

1649 the following is not valid for a time value: 

1650 

1651 .. code-block:: text 

1652 

1653 230000-0800 ;Invalid time format 

1654 

1655 **FORM #1 LOCAL TIME** 

1656 

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

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

1659 example, 11:00 PM: 

1660 

1661 .. code-block:: text 

1662 

1663 230000 

1664 

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

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

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

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

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

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

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

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

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

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

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

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

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

1678 

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

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

1681 time zone reference MUST be specified. 

1682 

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

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

1685 existence of "VTIMEZONE" calendar components in the iCalendar 

1686 object. 

1687 

1688 **FORM #2: UTC TIME** 

1689 

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

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

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

1693 

1694 .. code-block:: text 

1695 

1696 070000Z 

1697 

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

1699 properties whose time values are specified in UTC. 

1700 

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

1702 

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

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

1705 the appropriate time zone definition. 

1706 

1707 Example: 

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

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

1710 

1711 .. code-block:: text 

1712 

1713 083000 

1714 133000Z 

1715 TZID=America/New_York:083000 

1716 """ 

1717 

1718 def __init__(self, *args): 

1719 if len(args) == 1: 

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

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

1722 self.dt = args[0] 

1723 else: 

1724 self.dt = time(*args) 

1725 self.params = Parameters({"value": "TIME"}) 

1726 

1727 def to_ical(self): 

1728 return self.dt.strftime("%H%M%S") 

1729 

1730 @staticmethod 

1731 def from_ical(ical): 

1732 # TODO: timezone support 

1733 try: 

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

1735 return time(*timetuple) 

1736 except Exception as e: 

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

1738 

1739 

1740class vUri(str): 

1741 """URI 

1742 

1743 Value Name: 

1744 URI 

1745 

1746 Purpose: 

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

1748 uniform resource identifier (URI) type of reference to the 

1749 property value. 

1750 

1751 Format Definition: 

1752 This value type is defined by the following notation: 

1753 

1754 .. code-block:: text 

1755 

1756 uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ] 

1757 

1758 Description: 

1759 This value type might be used to reference binary 

1760 information, for values that are large, or otherwise undesirable 

1761 to include directly in the iCalendar object. 

1762 

1763 Property values with this value type MUST follow the generic URI 

1764 syntax defined in [RFC3986]. 

1765 

1766 When a property parameter value is a URI value type, the URI MUST 

1767 be specified as a quoted-string value. 

1768 

1769 Example: 

1770 The following is a URI for a network file: 

1771 

1772 .. code-block:: text 

1773 

1774 http://example.com/my-report.txt 

1775 

1776 .. code-block:: pycon 

1777 

1778 >>> from icalendar.prop import vUri 

1779 >>> uri = vUri.from_ical('http://example.com/my-report.txt') 

1780 >>> uri 

1781 'http://example.com/my-report.txt' 

1782 """ 

1783 

1784 params: Parameters 

1785 __slots__ = ("params",) 

1786 

1787 def __new__( 

1788 cls, 

1789 value: str, 

1790 encoding: str = DEFAULT_ENCODING, 

1791 /, 

1792 params: dict[str, Any] | None = None, 

1793 ): 

1794 if params is None: 

1795 params = {} 

1796 value = to_unicode(value, encoding=encoding) 

1797 self = super().__new__(cls, value) 

1798 self.params = Parameters(params) 

1799 return self 

1800 

1801 def to_ical(self): 

1802 return self.encode(DEFAULT_ENCODING) 

1803 

1804 @classmethod 

1805 def from_ical(cls, ical): 

1806 try: 

1807 return cls(ical) 

1808 except Exception as e: 

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

1810 

1811 

1812class vGeo: 

1813 """Geographic Position 

1814 

1815 Property Name: 

1816 GEO 

1817 

1818 Purpose: 

1819 This property specifies information related to the global 

1820 position for the activity specified by a calendar component. 

1821 

1822 Value Type: 

1823 FLOAT. The value MUST be two SEMICOLON-separated FLOAT values. 

1824 

1825 Property Parameters: 

1826 IANA and non-standard property parameters can be specified on 

1827 this property. 

1828 

1829 Conformance: 

1830 This property can be specified in "VEVENT" or "VTODO" 

1831 calendar components. 

1832 

1833 Description: 

1834 This property value specifies latitude and longitude, 

1835 in that order (i.e., "LAT LON" ordering). The longitude 

1836 represents the location east or west of the prime meridian as a 

1837 positive or negative real number, respectively. The longitude and 

1838 latitude values MAY be specified up to six decimal places, which 

1839 will allow for accuracy to within one meter of geographical 

1840 position. Receiving applications MUST accept values of this 

1841 precision and MAY truncate values of greater precision. 

1842 

1843 Example: 

1844 

1845 .. code-block:: text 

1846 

1847 GEO:37.386013;-122.082932 

1848 

1849 Parse vGeo: 

1850 

1851 .. code-block:: pycon 

1852 

1853 >>> from icalendar.prop import vGeo 

1854 >>> geo = vGeo.from_ical('37.386013;-122.082932') 

1855 >>> geo 

1856 (37.386013, -122.082932) 

1857 

1858 Add a geo location to an event: 

1859 

1860 .. code-block:: pycon 

1861 

1862 >>> from icalendar import Event 

1863 >>> event = Event() 

1864 >>> latitude = 37.386013 

1865 >>> longitude = -122.082932 

1866 >>> event.add('GEO', (latitude, longitude)) 

1867 >>> event['GEO'] 

1868 vGeo((37.386013, -122.082932)) 

1869 """ 

1870 

1871 params: Parameters 

1872 

1873 def __init__( 

1874 self, 

1875 geo: tuple[float | str | int, float | str | int], 

1876 /, 

1877 params: dict[str, Any] | None = None, 

1878 ): 

1879 """Create a new vGeo from a tuple of (latitude, longitude). 

1880 

1881 Raises: 

1882 ValueError: if geo is not a tuple of (latitude, longitude) 

1883 """ 

1884 if params is None: 

1885 params = {} 

1886 try: 

1887 latitude, longitude = (geo[0], geo[1]) 

1888 latitude = float(latitude) 

1889 longitude = float(longitude) 

1890 except Exception as e: 

1891 raise ValueError( 

1892 "Input must be (float, float) for latitude and longitude" 

1893 ) from e 

1894 self.latitude = latitude 

1895 self.longitude = longitude 

1896 self.params = Parameters(params) 

1897 

1898 def to_ical(self): 

1899 return f"{self.latitude};{self.longitude}" 

1900 

1901 @staticmethod 

1902 def from_ical(ical): 

1903 try: 

1904 latitude, longitude = ical.split(";") 

1905 return (float(latitude), float(longitude)) 

1906 except Exception as e: 

1907 raise ValueError(f"Expected 'float;float' , got: {ical}") from e 

1908 

1909 def __eq__(self, other): 

1910 return self.to_ical() == other.to_ical() 

1911 

1912 def __repr__(self): 

1913 """repr(self)""" 

1914 return f"{self.__class__.__name__}(({self.latitude}, {self.longitude}))" 

1915 

1916 

1917class vUTCOffset: 

1918 """UTC Offset 

1919 

1920 Value Name: 

1921 UTC-OFFSET 

1922 

1923 Purpose: 

1924 This value type is used to identify properties that contain 

1925 an offset from UTC to local time. 

1926 

1927 Format Definition: 

1928 This value type is defined by the following notation: 

1929 

1930 .. code-block:: text 

1931 

1932 utc-offset = time-numzone 

1933 

1934 time-numzone = ("+" / "-") time-hour time-minute [time-second] 

1935 

1936 Description: 

1937 The PLUS SIGN character MUST be specified for positive 

1938 UTC offsets (i.e., ahead of UTC). The HYPHEN-MINUS character MUST 

1939 be specified for negative UTC offsets (i.e., behind of UTC). The 

1940 value of "-0000" and "-000000" are not allowed. The time-second, 

1941 if present, MUST NOT be 60; if absent, it defaults to zero. 

1942 

1943 Example: 

1944 The following UTC offsets are given for standard time for 

1945 New York (five hours behind UTC) and Geneva (one hour ahead of 

1946 UTC): 

1947 

1948 .. code-block:: text 

1949 

1950 -0500 

1951 

1952 +0100 

1953 

1954 .. code-block:: pycon 

1955 

1956 >>> from icalendar.prop import vUTCOffset 

1957 >>> utc_offset = vUTCOffset.from_ical('-0500') 

1958 >>> utc_offset 

1959 datetime.timedelta(days=-1, seconds=68400) 

1960 >>> utc_offset = vUTCOffset.from_ical('+0100') 

1961 >>> utc_offset 

1962 datetime.timedelta(seconds=3600) 

1963 """ 

1964 

1965 params: Parameters 

1966 

1967 ignore_exceptions = False # if True, and we cannot parse this 

1968 

1969 # component, we will silently ignore 

1970 # it, rather than let the exception 

1971 # propagate upwards 

1972 

1973 def __init__(self, td, /, params: dict[str, Any] | None = None): 

1974 if params is None: 

1975 params = {} 

1976 if not isinstance(td, timedelta): 

1977 raise TypeError("Offset value MUST be a timedelta instance") 

1978 self.td = td 

1979 self.params = Parameters(params) 

1980 

1981 def to_ical(self): 

1982 if self.td < timedelta(0): 

1983 sign = "-%s" 

1984 td = timedelta(0) - self.td # get timedelta relative to 0 

1985 else: 

1986 # Google Calendar rejects '0000' but accepts '+0000' 

1987 sign = "+%s" 

1988 td = self.td 

1989 

1990 days, seconds = td.days, td.seconds 

1991 

1992 hours = abs(days * 24 + seconds // 3600) 

1993 minutes = abs((seconds % 3600) // 60) 

1994 seconds = abs(seconds % 60) 

1995 if seconds: 

1996 duration = f"{hours:02}{minutes:02}{seconds:02}" 

1997 else: 

1998 duration = f"{hours:02}{minutes:02}" 

1999 return sign % duration 

2000 

2001 @classmethod 

2002 def from_ical(cls, ical): 

2003 if isinstance(ical, cls): 

2004 return ical.td 

2005 try: 

2006 sign, hours, minutes, seconds = ( 

2007 ical[0:1], 

2008 int(ical[1:3]), 

2009 int(ical[3:5]), 

2010 int(ical[5:7] or 0), 

2011 ) 

2012 offset = timedelta(hours=hours, minutes=minutes, seconds=seconds) 

2013 except Exception as e: 

2014 raise ValueError(f"Expected utc offset, got: {ical}") from e 

2015 if not cls.ignore_exceptions and offset >= timedelta(hours=24): 

2016 raise ValueError(f"Offset must be less than 24 hours, was {ical}") 

2017 if sign == "-": 

2018 return -offset 

2019 return offset 

2020 

2021 def __eq__(self, other): 

2022 if not isinstance(other, vUTCOffset): 

2023 return False 

2024 return self.td == other.td 

2025 

2026 def __hash__(self): 

2027 return hash(self.td) 

2028 

2029 def __repr__(self): 

2030 return f"vUTCOffset({self.td!r})" 

2031 

2032 

2033class vInline(str): 

2034 """This is an especially dumb class that just holds raw unparsed text and 

2035 has parameters. Conversion of inline values are handled by the Component 

2036 class, so no further processing is needed. 

2037 """ 

2038 

2039 params: Parameters 

2040 __slots__ = ("params",) 

2041 

2042 def __new__( 

2043 cls, 

2044 value, 

2045 encoding=DEFAULT_ENCODING, 

2046 /, 

2047 params: dict[str, Any] | None = None, 

2048 ): 

2049 if params is None: 

2050 params = {} 

2051 value = to_unicode(value, encoding=encoding) 

2052 self = super().__new__(cls, value) 

2053 self.params = Parameters(params) 

2054 return self 

2055 

2056 def to_ical(self): 

2057 return self.encode(DEFAULT_ENCODING) 

2058 

2059 @classmethod 

2060 def from_ical(cls, ical): 

2061 return cls(ical) 

2062 

2063 

2064class TypesFactory(CaselessDict): 

2065 """All Value types defined in RFC 5545 are registered in this factory 

2066 class. 

2067 

2068 The value and parameter names don't overlap. So one factory is enough for 

2069 both kinds. 

2070 """ 

2071 

2072 def __init__(self, *args, **kwargs): 

2073 """Set keys to upper for initial dict""" 

2074 super().__init__(*args, **kwargs) 

2075 self.all_types = ( 

2076 vBinary, 

2077 vBoolean, 

2078 vCalAddress, 

2079 vDDDLists, 

2080 vDDDTypes, 

2081 vDate, 

2082 vDatetime, 

2083 vDuration, 

2084 vFloat, 

2085 vFrequency, 

2086 vGeo, 

2087 vInline, 

2088 vInt, 

2089 vPeriod, 

2090 vRecur, 

2091 vText, 

2092 vTime, 

2093 vUTCOffset, 

2094 vUri, 

2095 vWeekday, 

2096 vCategory, 

2097 ) 

2098 self["binary"] = vBinary 

2099 self["boolean"] = vBoolean 

2100 self["cal-address"] = vCalAddress 

2101 self["date"] = vDDDTypes 

2102 self["date-time"] = vDDDTypes 

2103 self["duration"] = vDDDTypes 

2104 self["float"] = vFloat 

2105 self["integer"] = vInt 

2106 self["period"] = vPeriod 

2107 self["recur"] = vRecur 

2108 self["text"] = vText 

2109 self["time"] = vTime 

2110 self["uri"] = vUri 

2111 self["utc-offset"] = vUTCOffset 

2112 self["geo"] = vGeo 

2113 self["inline"] = vInline 

2114 self["date-time-list"] = vDDDLists 

2115 self["categories"] = vCategory 

2116 

2117 ################################################# 

2118 # Property types 

2119 

2120 # These are the default types 

2121 types_map = CaselessDict( 

2122 { 

2123 #################################### 

2124 # Property value types 

2125 # Calendar Properties 

2126 "calscale": "text", 

2127 "method": "text", 

2128 "prodid": "text", 

2129 "version": "text", 

2130 # Descriptive Component Properties 

2131 "attach": "uri", 

2132 "categories": "categories", 

2133 "class": "text", 

2134 "comment": "text", 

2135 "description": "text", 

2136 "geo": "geo", 

2137 "location": "text", 

2138 "percent-complete": "integer", 

2139 "priority": "integer", 

2140 "resources": "text", 

2141 "status": "text", 

2142 "summary": "text", 

2143 # Date and Time Component Properties 

2144 "completed": "date-time", 

2145 "dtend": "date-time", 

2146 "due": "date-time", 

2147 "dtstart": "date-time", 

2148 "duration": "duration", 

2149 "freebusy": "period", 

2150 "transp": "text", 

2151 "refresh-interval": "duration", # RFC 7986 

2152 # Time Zone Component Properties 

2153 "tzid": "text", 

2154 "tzname": "text", 

2155 "tzoffsetfrom": "utc-offset", 

2156 "tzoffsetto": "utc-offset", 

2157 "tzurl": "uri", 

2158 # Relationship Component Properties 

2159 "attendee": "cal-address", 

2160 "contact": "text", 

2161 "organizer": "cal-address", 

2162 "recurrence-id": "date-time", 

2163 "related-to": "text", 

2164 "url": "uri", 

2165 "conference": "uri", # RFC 7986 

2166 "source": "uri", 

2167 "uid": "text", 

2168 # Recurrence Component Properties 

2169 "exdate": "date-time-list", 

2170 "exrule": "recur", 

2171 "rdate": "date-time-list", 

2172 "rrule": "recur", 

2173 # Alarm Component Properties 

2174 "action": "text", 

2175 "repeat": "integer", 

2176 "trigger": "duration", 

2177 "acknowledged": "date-time", 

2178 # Change Management Component Properties 

2179 "created": "date-time", 

2180 "dtstamp": "date-time", 

2181 "last-modified": "date-time", 

2182 "sequence": "integer", 

2183 # Miscellaneous Component Properties 

2184 "request-status": "text", 

2185 #################################### 

2186 # parameter types (luckily there is no name overlap) 

2187 "altrep": "uri", 

2188 "cn": "text", 

2189 "cutype": "text", 

2190 "delegated-from": "cal-address", 

2191 "delegated-to": "cal-address", 

2192 "dir": "uri", 

2193 "encoding": "text", 

2194 "fmttype": "text", 

2195 "fbtype": "text", 

2196 "language": "text", 

2197 "member": "cal-address", 

2198 "partstat": "text", 

2199 "range": "text", 

2200 "related": "text", 

2201 "reltype": "text", 

2202 "role": "text", 

2203 "rsvp": "boolean", 

2204 "sent-by": "cal-address", 

2205 "value": "text", 

2206 } 

2207 ) 

2208 

2209 def for_property(self, name, value_param: str | None = None) -> type: 

2210 """Returns the type class for a property or parameter. 

2211 

2212 Args: 

2213 name: Property or parameter name 

2214 value_param: Optional ``VALUE`` parameter, for example, "DATE", "DATE-TIME", or other string. 

2215 

2216 Returns: 

2217 The appropriate value type class 

2218 """ 

2219 # Special case: RDATE and EXDATE always use vDDDLists to support list values 

2220 # regardless of the VALUE parameter 

2221 if name.upper() in ("RDATE", "EXDATE"): 

2222 return self["date-time-list"] 

2223 

2224 # Only use VALUE parameter for known properties that support multiple value types 

2225 # (like DTSTART, DTEND, etc. which can be DATE or DATE-TIME) 

2226 # For unknown/custom properties, always use the default type from types_map 

2227 if value_param and name in self.types_map: 

2228 if value_param in self: 

2229 return self[value_param] 

2230 return self[self.types_map.get(name, "text")] 

2231 

2232 def to_ical(self, name, value): 

2233 """Encodes a named value from a primitive python type to an icalendar 

2234 encoded string. 

2235 """ 

2236 type_class = self.for_property(name) 

2237 return type_class(value).to_ical() 

2238 

2239 def from_ical(self, name, value): 

2240 """Decodes a named property or parameter value from an icalendar 

2241 encoded string to a primitive python type. 

2242 """ 

2243 type_class = self.for_property(name) 

2244 return type_class.from_ical(value) 

2245 

2246 

2247__all__ = [ 

2248 "DURATION_REGEX", 

2249 "WEEKDAY_RULE", 

2250 "TimeBase", 

2251 "TypesFactory", 

2252 "tzid_from_dt", 

2253 "tzid_from_tzinfo", 

2254 "vBinary", 

2255 "vBoolean", 

2256 "vCalAddress", 

2257 "vCategory", 

2258 "vDDDLists", 

2259 "vDDDTypes", 

2260 "vDate", 

2261 "vDatetime", 

2262 "vDuration", 

2263 "vFloat", 

2264 "vFrequency", 

2265 "vGeo", 

2266 "vInline", 

2267 "vInt", 

2268 "vMonth", 

2269 "vPeriod", 

2270 "vRecur", 

2271 "vSkip", 

2272 "vText", 

2273 "vTime", 

2274 "vUTCOffset", 

2275 "vUri", 

2276 "vWeekday", 

2277]