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

729 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, Optional, 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: Optional[dict[str, Any]] = 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: Optional[dict[str, Any]] = 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 """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: Optional[dict[str, Any]] = 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: Optional[dict[str, Any]] = 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: Optional[dict[str, Any]] = 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: Optional[dict[str, Any]] = 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 """Render and generates icalendar datetime format. 

812 

813 vDatetime is timezone aware and uses a timezone library. 

814 When a vDatetime object is created from an 

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

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

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

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

819 DATE-TIME components in the icalendar standard. 

820 """ 

821 

822 params: Parameters 

823 

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

825 if params is None: 

826 params = {} 

827 self.dt = dt 

828 self.params = Parameters(params) 

829 

830 def to_ical(self): 

831 dt = self.dt 

832 tzid = tzid_from_dt(dt) 

833 

834 s = ( 

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

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

837 ) 

838 if tzid == "UTC": 

839 s += "Z" 

840 elif tzid: 

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

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

843 

844 @staticmethod 

845 def from_ical(ical, timezone=None): 

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

847 

848 Format: 

849 

850 .. code-block:: text 

851 

852 YYYYMMDDTHHMMSS 

853 

854 .. code-block:: pycon 

855 

856 >>> from icalendar import vDatetime 

857 >>> vDatetime.from_ical("20210302T101500") 

858 datetime.datetime(2021, 3, 2, 10, 15) 

859 

860 >>> vDatetime.from_ical("20210302T101500", "America/New_York") 

861 datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='America/New_York')) 

862 

863 >>> from zoneinfo import ZoneInfo 

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

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

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

867 """ # noqa: E501 

868 tzinfo = None 

869 if isinstance(timezone, str): 

870 tzinfo = tzp.timezone(timezone) 

871 elif timezone is not None: 

872 tzinfo = timezone 

873 

874 try: 

875 timetuple = ( 

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

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

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

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

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

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

882 ) 

883 if tzinfo: 

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

885 if not ical[15:]: 

886 return datetime(*timetuple) # noqa: DTZ001 

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

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

889 except Exception as e: 

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

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

892 

893 

894class vDuration(TimeBase): 

895 """Duration 

896 

897 Value Name: 

898 DURATION 

899 

900 Purpose: 

901 This value type is used to identify properties that contain 

902 a duration of time. 

903 

904 Format Definition: 

905 This value type is defined by the following notation: 

906 

907 .. code-block:: text 

908 

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

910 

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

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

913 dur-week = 1*DIGIT "W" 

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

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

916 dur-second = 1*DIGIT "S" 

917 dur-day = 1*DIGIT "D" 

918 

919 Description: 

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

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

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

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

924 represent nominal durations (weeks and days) and accurate 

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

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

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

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

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

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

931 computation of the exact duration requires the subtraction or 

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

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

934 When computing an exact duration, the greatest order time 

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

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

937 minutes, and number of seconds. 

938 

939 Example: 

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

941 

942 .. code-block:: text 

943 

944 P15DT5H0M20S 

945 

946 A duration of 7 weeks would be: 

947 

948 .. code-block:: text 

949 

950 P7W 

951 

952 .. code-block:: pycon 

953 

954 >>> from icalendar.prop import vDuration 

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

956 >>> duration 

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

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

959 >>> duration 

960 datetime.timedelta(days=49) 

961 """ 

962 

963 params: Parameters 

964 

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

966 if params is None: 

967 params = {} 

968 if not isinstance(td, timedelta): 

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

970 self.td = td 

971 self.params = Parameters(params) 

972 

973 def to_ical(self): 

974 sign = "" 

975 td = self.td 

976 if td.days < 0: 

977 sign = "-" 

978 td = -td 

979 timepart = "" 

980 if td.seconds: 

981 timepart = "T" 

982 hours = td.seconds // 3600 

983 minutes = td.seconds % 3600 // 60 

984 seconds = td.seconds % 60 

985 if hours: 

986 timepart += f"{hours}H" 

987 if minutes or (hours and seconds): 

988 timepart += f"{minutes}M" 

989 if seconds: 

990 timepart += f"{seconds}S" 

991 if td.days == 0 and timepart: 

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

993 return ( 

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

995 + b"P" 

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

997 + b"D" 

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

999 ) 

1000 

1001 @staticmethod 

1002 def from_ical(ical): 

1003 match = DURATION_REGEX.match(ical) 

1004 if not match: 

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

1006 

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

1008 value = timedelta( 

1009 weeks=int(weeks or 0), 

1010 days=int(days or 0), 

1011 hours=int(hours or 0), 

1012 minutes=int(minutes or 0), 

1013 seconds=int(seconds or 0), 

1014 ) 

1015 

1016 if sign == "-": 

1017 value = -value 

1018 

1019 return value 

1020 

1021 @property 

1022 def dt(self) -> timedelta: 

1023 """The time delta for compatibility.""" 

1024 return self.td 

1025 

1026 

1027class vPeriod(TimeBase): 

1028 """Period of Time 

1029 

1030 Value Name: 

1031 PERIOD 

1032 

1033 Purpose: 

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

1035 precise period of time. 

1036 

1037 Format Definition: 

1038 This value type is defined by the following notation: 

1039 

1040 .. code-block:: text 

1041 

1042 period = period-explicit / period-start 

1043 

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

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

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

1047 ; be before the end. 

1048 

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

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

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

1052 ; of time. 

1053 

1054 Description: 

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

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

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

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

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

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

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

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

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

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

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

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

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

1068 

1069 Example: 

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

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

1072 

1073 .. code-block:: text 

1074 

1075 19970101T180000Z/19970102T070000Z 

1076 

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

1078 and 30 minutes would be: 

1079 

1080 .. code-block:: text 

1081 

1082 19970101T180000Z/PT5H30M 

1083 

1084 .. code-block:: pycon 

1085 

1086 >>> from icalendar.prop import vPeriod 

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

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

1089 """ 

1090 

1091 params: Parameters 

1092 

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

1094 start, end_or_duration = per 

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

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

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

1098 raise TypeError( 

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

1100 ) 

1101 by_duration = 0 

1102 if isinstance(end_or_duration, timedelta): 

1103 by_duration = 1 

1104 duration = end_or_duration 

1105 end = start + duration 

1106 else: 

1107 end = end_or_duration 

1108 duration = end - start 

1109 if start > end: 

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

1111 

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

1113 # set the timezone identifier 

1114 # does not support different timezones for start and end 

1115 tzid = tzid_from_dt(start) 

1116 if tzid: 

1117 self.params["TZID"] = tzid 

1118 

1119 self.start = start 

1120 self.end = end 

1121 self.by_duration = by_duration 

1122 self.duration = duration 

1123 

1124 def overlaps(self, other): 

1125 if self.start > other.start: 

1126 return other.overlaps(self) 

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

1128 

1129 def to_ical(self): 

1130 if self.by_duration: 

1131 return ( 

1132 vDatetime(self.start).to_ical() 

1133 + b"/" 

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

1135 ) 

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

1137 

1138 @staticmethod 

1139 def from_ical(ical, timezone=None): 

1140 try: 

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

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

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

1144 except Exception as e: 

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

1146 return (start, end_or_duration) 

1147 

1148 def __repr__(self): 

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

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

1151 

1152 @property 

1153 def dt(self): 

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

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

1156 

1157 from icalendar.param import FBTYPE 

1158 

1159 

1160class vWeekday(str): 

1161 """Either a ``weekday`` or a ``weekdaynum`` 

1162 

1163 .. code-block:: pycon 

1164 

1165 >>> from icalendar import vWeekday 

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

1167 'MO' 

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

1169 2 

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

1171 'FR' 

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

1173 -1 

1174 

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

1176 

1177 .. code-block:: text 

1178 

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

1180 plus = "+" 

1181 minus = "-" 

1182 ordwk = 1*2DIGIT ;1 to 53 

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

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

1185 ;FRIDAY, and SATURDAY days of the week. 

1186 

1187 """ 

1188 

1189 params: Parameters 

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

1191 

1192 week_days = CaselessDict( 

1193 { 

1194 "SU": 0, 

1195 "MO": 1, 

1196 "TU": 2, 

1197 "WE": 3, 

1198 "TH": 4, 

1199 "FR": 5, 

1200 "SA": 6, 

1201 } 

1202 ) 

1203 

1204 def __new__( 

1205 cls, 

1206 value, 

1207 encoding=DEFAULT_ENCODING, 

1208 /, 

1209 params: Optional[dict[str, Any]] = None, 

1210 ): 

1211 if params is None: 

1212 params = {} 

1213 value = to_unicode(value, encoding=encoding) 

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

1215 match = WEEKDAY_RULE.match(self) 

1216 if match is None: 

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

1218 match = match.groupdict() 

1219 sign = match["signal"] 

1220 weekday = match["weekday"] 

1221 relative = match["relative"] 

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

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

1224 self.weekday = weekday or None 

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

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

1227 self.relative *= -1 

1228 self.params = Parameters(params) 

1229 return self 

1230 

1231 def to_ical(self): 

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

1233 

1234 @classmethod 

1235 def from_ical(cls, ical): 

1236 try: 

1237 return cls(ical.upper()) 

1238 except Exception as e: 

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

1240 

1241 

1242class vFrequency(str): 

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

1244 

1245 params: Parameters 

1246 __slots__ = ("params",) 

1247 

1248 frequencies = CaselessDict( 

1249 { 

1250 "SECONDLY": "SECONDLY", 

1251 "MINUTELY": "MINUTELY", 

1252 "HOURLY": "HOURLY", 

1253 "DAILY": "DAILY", 

1254 "WEEKLY": "WEEKLY", 

1255 "MONTHLY": "MONTHLY", 

1256 "YEARLY": "YEARLY", 

1257 } 

1258 ) 

1259 

1260 def __new__( 

1261 cls, 

1262 value, 

1263 encoding=DEFAULT_ENCODING, 

1264 /, 

1265 params: Optional[dict[str, Any]] = None, 

1266 ): 

1267 if params is None: 

1268 params = {} 

1269 value = to_unicode(value, encoding=encoding) 

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

1271 if self not in vFrequency.frequencies: 

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

1273 self.params = Parameters(params) 

1274 return self 

1275 

1276 def to_ical(self): 

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

1278 

1279 @classmethod 

1280 def from_ical(cls, ical): 

1281 try: 

1282 return cls(ical.upper()) 

1283 except Exception as e: 

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

1285 

1286 

1287class vMonth(int): 

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

1289 

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

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

1292 

1293 .. code-block:: pycon 

1294 

1295 >>> from icalendar import vMonth 

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

1297 vMonth('1') 

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

1299 vMonth('5L') 

1300 >>> vMonth(1).leap 

1301 False 

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

1303 True 

1304 

1305 Definition from RFC: 

1306 

1307 .. code-block:: text 

1308 

1309 type-bymonth = element bymonth { 

1310 xsd:positiveInteger | 

1311 xsd:string 

1312 } 

1313 """ 

1314 

1315 params: Parameters 

1316 

1317 def __new__( 

1318 cls, month: Union[str, int], /, params: Optional[dict[str, Any]] = None 

1319 ): 

1320 if params is None: 

1321 params = {} 

1322 if isinstance(month, vMonth): 

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

1324 if isinstance(month, str): 

1325 if month.isdigit(): 

1326 month_index = int(month) 

1327 leap = False 

1328 else: 

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

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

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

1332 leap = True 

1333 else: 

1334 leap = False 

1335 month_index = int(month) 

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

1337 self.leap = leap 

1338 self.params = Parameters(params) 

1339 return self 

1340 

1341 def to_ical(self) -> bytes: 

1342 """The ical representation.""" 

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

1344 

1345 @classmethod 

1346 def from_ical(cls, ical: str): 

1347 return cls(ical) 

1348 

1349 @property 

1350 def leap(self) -> bool: 

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

1352 return self._leap 

1353 

1354 @leap.setter 

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

1356 self._leap = value 

1357 

1358 def __repr__(self) -> str: 

1359 """repr(self)""" 

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

1361 

1362 def __str__(self) -> str: 

1363 """str(self)""" 

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

1365 

1366 

1367class vSkip(vText, Enum): 

1368 """Skip values for RRULE. 

1369 

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

1371 

1372 OMIT is the default value. 

1373 

1374 Examples: 

1375 

1376 .. code-block:: pycon 

1377 

1378 >>> from icalendar import vSkip 

1379 >>> vSkip.OMIT 

1380 vSkip('OMIT') 

1381 >>> vSkip.FORWARD 

1382 vSkip('FORWARD') 

1383 >>> vSkip.BACKWARD 

1384 vSkip('BACKWARD') 

1385 """ 

1386 

1387 OMIT = "OMIT" 

1388 FORWARD = "FORWARD" 

1389 BACKWARD = "BACKWARD" 

1390 

1391 __reduce_ex__ = Enum.__reduce_ex__ 

1392 

1393 def __repr__(self): 

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

1395 

1396 

1397class vRecur(CaselessDict): 

1398 """Recurrence definition. 

1399 

1400 Property Name: 

1401 RRULE 

1402 

1403 Purpose: 

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

1405 journal entries, or time zone definitions. 

1406 

1407 Value Type: 

1408 RECUR 

1409 

1410 Property Parameters: 

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

1412 

1413 Conformance: 

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

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

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

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

1418 

1419 Description: 

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

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

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

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

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

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

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

1427 value not synchronized with the recurrence rule is undefined. 

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

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

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

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

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

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

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

1435 

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

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

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

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

1440 same local time regardless of time zone changes. 

1441 

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

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

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

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

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

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

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

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

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

1451 "RDATE" property of PERIOD value type. 

1452 

1453 Examples: 

1454 The following RRULE specifies daily events for 10 occurrences. 

1455 

1456 .. code-block:: text 

1457 

1458 RRULE:FREQ=DAILY;COUNT=10 

1459 

1460 Below, we parse the RRULE ical string. 

1461 

1462 .. code-block:: pycon 

1463 

1464 >>> from icalendar.prop import vRecur 

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

1466 >>> rrule 

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

1468 

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

1470 :class:`icalendar.cal.Todo`. 

1471 

1472 .. code-block:: pycon 

1473 

1474 >>> from icalendar import Event 

1475 >>> event = Event() 

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

1477 >>> event.rrules 

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

1479 """ # noqa: E501 

1480 

1481 params: Parameters 

1482 

1483 frequencies = [ 

1484 "SECONDLY", 

1485 "MINUTELY", 

1486 "HOURLY", 

1487 "DAILY", 

1488 "WEEKLY", 

1489 "MONTHLY", 

1490 "YEARLY", 

1491 ] 

1492 

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

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

1495 canonical_order = ( 

1496 "RSCALE", 

1497 "FREQ", 

1498 "UNTIL", 

1499 "COUNT", 

1500 "INTERVAL", 

1501 "BYSECOND", 

1502 "BYMINUTE", 

1503 "BYHOUR", 

1504 "BYDAY", 

1505 "BYWEEKDAY", 

1506 "BYMONTHDAY", 

1507 "BYYEARDAY", 

1508 "BYWEEKNO", 

1509 "BYMONTH", 

1510 "BYSETPOS", 

1511 "WKST", 

1512 "SKIP", 

1513 ) 

1514 

1515 types = CaselessDict( 

1516 { 

1517 "COUNT": vInt, 

1518 "INTERVAL": vInt, 

1519 "BYSECOND": vInt, 

1520 "BYMINUTE": vInt, 

1521 "BYHOUR": vInt, 

1522 "BYWEEKNO": vInt, 

1523 "BYMONTHDAY": vInt, 

1524 "BYYEARDAY": vInt, 

1525 "BYMONTH": vMonth, 

1526 "UNTIL": vDDDTypes, 

1527 "BYSETPOS": vInt, 

1528 "WKST": vWeekday, 

1529 "BYDAY": vWeekday, 

1530 "FREQ": vFrequency, 

1531 "BYWEEKDAY": vWeekday, 

1532 "SKIP": vSkip, 

1533 } 

1534 ) 

1535 

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

1537 if params is None: 

1538 params = {} 

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

1540 # we have a string as an argument. 

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

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

1543 if not isinstance(v, SEQUENCE_TYPES): 

1544 kwargs[k] = [v] 

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

1546 self.params = Parameters(params) 

1547 

1548 def to_ical(self): 

1549 result = [] 

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

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

1552 if not isinstance(vals, SEQUENCE_TYPES): 

1553 vals = [vals] # noqa: PLW2901 

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

1555 

1556 # CaselessDict keys are always unicode 

1557 param_key = key.encode(DEFAULT_ENCODING) 

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

1559 

1560 return b";".join(result) 

1561 

1562 @classmethod 

1563 def parse_type(cls, key, values): 

1564 # integers 

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

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

1567 

1568 @classmethod 

1569 def from_ical(cls, ical: str): 

1570 if isinstance(ical, cls): 

1571 return ical 

1572 try: 

1573 recur = cls() 

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

1575 try: 

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

1577 except ValueError: 

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

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

1580 continue 

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

1582 return cls(recur) 

1583 except ValueError: 

1584 raise 

1585 except Exception as e: 

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

1587 

1588 

1589class vTime(TimeBase): 

1590 """Time 

1591 

1592 Value Name: 

1593 TIME 

1594 

1595 Purpose: 

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

1597 time of day. 

1598 

1599 Format Definition: 

1600 This value type is defined by the following notation: 

1601 

1602 .. code-block:: text 

1603 

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

1605 

1606 time-hour = 2DIGIT ;00-23 

1607 time-minute = 2DIGIT ;00-59 

1608 time-second = 2DIGIT ;00-60 

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

1610 

1611 time-utc = "Z" 

1612 

1613 Description: 

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

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

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

1617 vText) is defined for this value type. 

1618 

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

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

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

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

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

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

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

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

1627 

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

1629 type expresses time values in three forms: 

1630 

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

1632 the following is not valid for a time value: 

1633 

1634 .. code-block:: text 

1635 

1636 230000-0800 ;Invalid time format 

1637 

1638 **FORM #1 LOCAL TIME** 

1639 

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

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

1642 example, 11:00 PM: 

1643 

1644 .. code-block:: text 

1645 

1646 230000 

1647 

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

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

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

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

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

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

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

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

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

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

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

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

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

1661 

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

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

1664 time zone reference MUST be specified. 

1665 

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

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

1668 existence of "VTIMEZONE" calendar components in the iCalendar 

1669 object. 

1670 

1671 **FORM #2: UTC TIME** 

1672 

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

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

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

1676 

1677 .. code-block:: text 

1678 

1679 070000Z 

1680 

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

1682 properties whose time values are specified in UTC. 

1683 

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

1685 

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

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

1688 the appropriate time zone definition. 

1689 

1690 Example: 

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

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

1693 

1694 .. code-block:: text 

1695 

1696 083000 

1697 133000Z 

1698 TZID=America/New_York:083000 

1699 """ 

1700 

1701 def __init__(self, *args): 

1702 if len(args) == 1: 

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

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

1705 self.dt = args[0] 

1706 else: 

1707 self.dt = time(*args) 

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

1709 

1710 def to_ical(self): 

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

1712 

1713 @staticmethod 

1714 def from_ical(ical): 

1715 # TODO: timezone support 

1716 try: 

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

1718 return time(*timetuple) 

1719 except Exception as e: 

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

1721 

1722 

1723class vUri(str): 

1724 """URI 

1725 

1726 Value Name: 

1727 URI 

1728 

1729 Purpose: 

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

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

1732 property value. 

1733 

1734 Format Definition: 

1735 This value type is defined by the following notation: 

1736 

1737 .. code-block:: text 

1738 

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

1740 

1741 Description: 

1742 This value type might be used to reference binary 

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

1744 to include directly in the iCalendar object. 

1745 

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

1747 syntax defined in [RFC3986]. 

1748 

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

1750 be specified as a quoted-string value. 

1751 

1752 Example: 

1753 The following is a URI for a network file: 

1754 

1755 .. code-block:: text 

1756 

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

1758 

1759 .. code-block:: pycon 

1760 

1761 >>> from icalendar.prop import vUri 

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

1763 >>> uri 

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

1765 """ 

1766 

1767 params: Parameters 

1768 __slots__ = ("params",) 

1769 

1770 def __new__( 

1771 cls, 

1772 value: str, 

1773 encoding: str = DEFAULT_ENCODING, 

1774 /, 

1775 params: Optional[dict[str, Any]] = None, 

1776 ): 

1777 if params is None: 

1778 params = {} 

1779 value = to_unicode(value, encoding=encoding) 

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

1781 self.params = Parameters(params) 

1782 return self 

1783 

1784 def to_ical(self): 

1785 return self.encode(DEFAULT_ENCODING) 

1786 

1787 @classmethod 

1788 def from_ical(cls, ical): 

1789 try: 

1790 return cls(ical) 

1791 except Exception as e: 

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

1793 

1794 

1795class vGeo: 

1796 """Geographic Position 

1797 

1798 Property Name: 

1799 GEO 

1800 

1801 Purpose: 

1802 This property specifies information related to the global 

1803 position for the activity specified by a calendar component. 

1804 

1805 Value Type: 

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

1807 

1808 Property Parameters: 

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

1810 this property. 

1811 

1812 Conformance: 

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

1814 calendar components. 

1815 

1816 Description: 

1817 This property value specifies latitude and longitude, 

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

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

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

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

1822 will allow for accuracy to within one meter of geographical 

1823 position. Receiving applications MUST accept values of this 

1824 precision and MAY truncate values of greater precision. 

1825 

1826 Example: 

1827 

1828 .. code-block:: text 

1829 

1830 GEO:37.386013;-122.082932 

1831 

1832 Parse vGeo: 

1833 

1834 .. code-block:: pycon 

1835 

1836 >>> from icalendar.prop import vGeo 

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

1838 >>> geo 

1839 (37.386013, -122.082932) 

1840 

1841 Add a geo location to an event: 

1842 

1843 .. code-block:: pycon 

1844 

1845 >>> from icalendar import Event 

1846 >>> event = Event() 

1847 >>> latitude = 37.386013 

1848 >>> longitude = -122.082932 

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

1850 >>> event['GEO'] 

1851 vGeo((37.386013, -122.082932)) 

1852 """ 

1853 

1854 params: Parameters 

1855 

1856 def __init__( 

1857 self, 

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

1859 /, 

1860 params: Optional[dict[str, Any]] = None, 

1861 ): 

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

1863 

1864 Raises: 

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

1866 """ 

1867 if params is None: 

1868 params = {} 

1869 try: 

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

1871 latitude = float(latitude) 

1872 longitude = float(longitude) 

1873 except Exception as e: 

1874 raise ValueError( 

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

1876 ) from e 

1877 self.latitude = latitude 

1878 self.longitude = longitude 

1879 self.params = Parameters(params) 

1880 

1881 def to_ical(self): 

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

1883 

1884 @staticmethod 

1885 def from_ical(ical): 

1886 try: 

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

1888 return (float(latitude), float(longitude)) 

1889 except Exception as e: 

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

1891 

1892 def __eq__(self, other): 

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

1894 

1895 def __repr__(self): 

1896 """repr(self)""" 

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

1898 

1899 

1900class vUTCOffset: 

1901 """UTC Offset 

1902 

1903 Value Name: 

1904 UTC-OFFSET 

1905 

1906 Purpose: 

1907 This value type is used to identify properties that contain 

1908 an offset from UTC to local time. 

1909 

1910 Format Definition: 

1911 This value type is defined by the following notation: 

1912 

1913 .. code-block:: text 

1914 

1915 utc-offset = time-numzone 

1916 

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

1918 

1919 Description: 

1920 The PLUS SIGN character MUST be specified for positive 

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

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

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

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

1925 

1926 Example: 

1927 The following UTC offsets are given for standard time for 

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

1929 UTC): 

1930 

1931 .. code-block:: text 

1932 

1933 -0500 

1934 

1935 +0100 

1936 

1937 .. code-block:: pycon 

1938 

1939 >>> from icalendar.prop import vUTCOffset 

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

1941 >>> utc_offset 

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

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

1944 >>> utc_offset 

1945 datetime.timedelta(seconds=3600) 

1946 """ 

1947 

1948 params: Parameters 

1949 

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

1951 

1952 # component, we will silently ignore 

1953 # it, rather than let the exception 

1954 # propagate upwards 

1955 

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

1957 if params is None: 

1958 params = {} 

1959 if not isinstance(td, timedelta): 

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

1961 self.td = td 

1962 self.params = Parameters(params) 

1963 

1964 def to_ical(self): 

1965 if self.td < timedelta(0): 

1966 sign = "-%s" 

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

1968 else: 

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

1970 sign = "+%s" 

1971 td = self.td 

1972 

1973 days, seconds = td.days, td.seconds 

1974 

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

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

1977 seconds = abs(seconds % 60) 

1978 if seconds: 

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

1980 else: 

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

1982 return sign % duration 

1983 

1984 @classmethod 

1985 def from_ical(cls, ical): 

1986 if isinstance(ical, cls): 

1987 return ical.td 

1988 try: 

1989 sign, hours, minutes, seconds = ( 

1990 ical[0:1], 

1991 int(ical[1:3]), 

1992 int(ical[3:5]), 

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

1994 ) 

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

1996 except Exception as e: 

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

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

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

2000 if sign == "-": 

2001 return -offset 

2002 return offset 

2003 

2004 def __eq__(self, other): 

2005 if not isinstance(other, vUTCOffset): 

2006 return False 

2007 return self.td == other.td 

2008 

2009 def __hash__(self): 

2010 return hash(self.td) 

2011 

2012 def __repr__(self): 

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

2014 

2015 

2016class vInline(str): 

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

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

2019 class, so no further processing is needed. 

2020 """ 

2021 

2022 params: Parameters 

2023 __slots__ = ("params",) 

2024 

2025 def __new__( 

2026 cls, 

2027 value, 

2028 encoding=DEFAULT_ENCODING, 

2029 /, 

2030 params: Optional[dict[str, Any]] = None, 

2031 ): 

2032 if params is None: 

2033 params = {} 

2034 value = to_unicode(value, encoding=encoding) 

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

2036 self.params = Parameters(params) 

2037 return self 

2038 

2039 def to_ical(self): 

2040 return self.encode(DEFAULT_ENCODING) 

2041 

2042 @classmethod 

2043 def from_ical(cls, ical): 

2044 return cls(ical) 

2045 

2046 

2047class TypesFactory(CaselessDict): 

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

2049 class. 

2050 

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

2052 both kinds. 

2053 """ 

2054 

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

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

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

2058 self.all_types = ( 

2059 vBinary, 

2060 vBoolean, 

2061 vCalAddress, 

2062 vDDDLists, 

2063 vDDDTypes, 

2064 vDate, 

2065 vDatetime, 

2066 vDuration, 

2067 vFloat, 

2068 vFrequency, 

2069 vGeo, 

2070 vInline, 

2071 vInt, 

2072 vPeriod, 

2073 vRecur, 

2074 vText, 

2075 vTime, 

2076 vUTCOffset, 

2077 vUri, 

2078 vWeekday, 

2079 vCategory, 

2080 ) 

2081 self["binary"] = vBinary 

2082 self["boolean"] = vBoolean 

2083 self["cal-address"] = vCalAddress 

2084 self["date"] = vDDDTypes 

2085 self["date-time"] = vDDDTypes 

2086 self["duration"] = vDDDTypes 

2087 self["float"] = vFloat 

2088 self["integer"] = vInt 

2089 self["period"] = vPeriod 

2090 self["recur"] = vRecur 

2091 self["text"] = vText 

2092 self["time"] = vTime 

2093 self["uri"] = vUri 

2094 self["utc-offset"] = vUTCOffset 

2095 self["geo"] = vGeo 

2096 self["inline"] = vInline 

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

2098 self["categories"] = vCategory 

2099 

2100 ################################################# 

2101 # Property types 

2102 

2103 # These are the default types 

2104 types_map = CaselessDict( 

2105 { 

2106 #################################### 

2107 # Property value types 

2108 # Calendar Properties 

2109 "calscale": "text", 

2110 "method": "text", 

2111 "prodid": "text", 

2112 "version": "text", 

2113 # Descriptive Component Properties 

2114 "attach": "uri", 

2115 "categories": "categories", 

2116 "class": "text", 

2117 "comment": "text", 

2118 "description": "text", 

2119 "geo": "geo", 

2120 "location": "text", 

2121 "percent-complete": "integer", 

2122 "priority": "integer", 

2123 "resources": "text", 

2124 "status": "text", 

2125 "summary": "text", 

2126 # Date and Time Component Properties 

2127 "completed": "date-time", 

2128 "dtend": "date-time", 

2129 "due": "date-time", 

2130 "dtstart": "date-time", 

2131 "duration": "duration", 

2132 "freebusy": "period", 

2133 "transp": "text", 

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

2135 # Time Zone Component Properties 

2136 "tzid": "text", 

2137 "tzname": "text", 

2138 "tzoffsetfrom": "utc-offset", 

2139 "tzoffsetto": "utc-offset", 

2140 "tzurl": "uri", 

2141 # Relationship Component Properties 

2142 "attendee": "cal-address", 

2143 "contact": "text", 

2144 "organizer": "cal-address", 

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

2146 "related-to": "text", 

2147 "url": "uri", 

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

2149 "source": "uri", 

2150 "uid": "text", 

2151 # Recurrence Component Properties 

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

2153 "exrule": "recur", 

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

2155 "rrule": "recur", 

2156 # Alarm Component Properties 

2157 "action": "text", 

2158 "repeat": "integer", 

2159 "trigger": "duration", 

2160 "acknowledged": "date-time", 

2161 # Change Management Component Properties 

2162 "created": "date-time", 

2163 "dtstamp": "date-time", 

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

2165 "sequence": "integer", 

2166 # Miscellaneous Component Properties 

2167 "request-status": "text", 

2168 #################################### 

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

2170 "altrep": "uri", 

2171 "cn": "text", 

2172 "cutype": "text", 

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

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

2175 "dir": "uri", 

2176 "encoding": "text", 

2177 "fmttype": "text", 

2178 "fbtype": "text", 

2179 "language": "text", 

2180 "member": "cal-address", 

2181 "partstat": "text", 

2182 "range": "text", 

2183 "related": "text", 

2184 "reltype": "text", 

2185 "role": "text", 

2186 "rsvp": "boolean", 

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

2188 "value": "text", 

2189 } 

2190 ) 

2191 

2192 def for_property(self, name): 

2193 """Returns a the default type for a property or parameter""" 

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

2195 

2196 def to_ical(self, name, value): 

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

2198 encoded string. 

2199 """ 

2200 type_class = self.for_property(name) 

2201 return type_class(value).to_ical() 

2202 

2203 def from_ical(self, name, value): 

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

2205 encoded string to a primitive python type. 

2206 """ 

2207 type_class = self.for_property(name) 

2208 return type_class.from_ical(value) 

2209 

2210 

2211__all__ = [ 

2212 "DURATION_REGEX", 

2213 "WEEKDAY_RULE", 

2214 "TimeBase", 

2215 "TypesFactory", 

2216 "tzid_from_dt", 

2217 "tzid_from_tzinfo", 

2218 "vBinary", 

2219 "vBoolean", 

2220 "vCalAddress", 

2221 "vCategory", 

2222 "vDDDLists", 

2223 "vDDDTypes", 

2224 "vDate", 

2225 "vDatetime", 

2226 "vDuration", 

2227 "vFloat", 

2228 "vFrequency", 

2229 "vGeo", 

2230 "vInline", 

2231 "vInt", 

2232 "vMonth", 

2233 "vPeriod", 

2234 "vRecur", 

2235 "vSkip", 

2236 "vText", 

2237 "vTime", 

2238 "vUTCOffset", 

2239 "vUri", 

2240 "vWeekday", 

2241]