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
50import uuid
51from datetime import date, datetime, time, timedelta
52from typing import Any, Union
53
54from icalendar.caselessdict import CaselessDict
55from icalendar.enums import Enum
56from icalendar.error import InvalidCalendar
57from icalendar.parser import Parameters, escape_char, unescape_char
58from icalendar.parser_tools import (
59 DEFAULT_ENCODING,
60 ICAL_TYPE,
61 SEQUENCE_TYPES,
62 from_unicode,
63 to_unicode,
64)
65from icalendar.timezone import tzid_from_dt, tzid_from_tzinfo, tzp
66from icalendar.tools import to_datetime
67
68DURATION_REGEX = re.compile(
69 r"([-+]?)P(?:(\d+)W)?(?:(\d+)D)?" r"(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$"
70)
71
72WEEKDAY_RULE = re.compile(
73 r"(?P<signal>[+-]?)(?P<relative>[\d]{0,2})" r"(?P<weekday>[\w]{2})$"
74)
75
76
77class vBinary:
78 """Binary property values are base 64 encoded."""
79
80 params: Parameters
81 obj: str
82
83 def __init__(self, obj, params: dict[str, str] | None = None):
84 self.obj = to_unicode(obj)
85 self.params = Parameters(encoding="BASE64", value="BINARY")
86 if params:
87 self.params.update(params)
88
89 def __repr__(self):
90 return f"vBinary({self.to_ical()})"
91
92 def to_ical(self):
93 return binascii.b2a_base64(self.obj.encode("utf-8"))[:-1]
94
95 @staticmethod
96 def from_ical(ical):
97 try:
98 return base64.b64decode(ical)
99 except ValueError as e:
100 raise ValueError("Not valid base 64 encoding.") from e
101
102 def __eq__(self, other):
103 """self == other"""
104 return isinstance(other, vBinary) and self.obj == other.obj
105
106
107class vBoolean(int):
108 """Boolean
109
110 Value Name: BOOLEAN
111
112 Purpose: This value type is used to identify properties that contain
113 either a "TRUE" or "FALSE" Boolean value.
114
115 Format Definition: This value type is defined by the following
116 notation:
117
118 .. code-block:: text
119
120 boolean = "TRUE" / "FALSE"
121
122 Description: These values are case-insensitive text. No additional
123 content value encoding is defined for this value type.
124
125 Example: The following is an example of a hypothetical property that
126 has a BOOLEAN value type:
127
128 .. code-block:: python
129
130 TRUE
131
132 .. code-block:: pycon
133
134 >>> from icalendar.prop import vBoolean
135 >>> boolean = vBoolean.from_ical('TRUE')
136 >>> boolean
137 True
138 >>> boolean = vBoolean.from_ical('FALSE')
139 >>> boolean
140 False
141 >>> boolean = vBoolean.from_ical('True')
142 >>> boolean
143 True
144 """
145
146 params: Parameters
147
148 BOOL_MAP = CaselessDict({"true": True, "false": False})
149
150 def __new__(cls, *args, params: dict[str, Any] | None = None, **kwargs):
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 value = to_unicode(value, encoding=encoding)
180 self = super().__new__(cls, value)
181 self.encoding = encoding
182 self.params = Parameters(params)
183 return self
184
185 def __repr__(self) -> str:
186 return f"vText({self.to_ical()!r})"
187
188 def to_ical(self) -> bytes:
189 return escape_char(self).encode(self.encoding)
190
191 @classmethod
192 def from_ical(cls, ical: ICAL_TYPE):
193 ical_unesc = unescape_char(ical)
194 return cls(ical_unesc)
195
196 @property
197 def ical_value(self) -> str:
198 """The string value of the text."""
199 return str(self)
200
201 from icalendar.param import ALTREP, GAP, LANGUAGE, RELTYPE, VALUE
202
203
204class vCalAddress(str):
205 r"""Calendar User Address
206
207 Value Name:
208 CAL-ADDRESS
209
210 Purpose:
211 This value type is used to identify properties that contain a
212 calendar user address.
213
214 Description:
215 The value is a URI as defined by [RFC3986] or any other
216 IANA-registered form for a URI. When used to address an Internet
217 email transport address for a calendar user, the value MUST be a
218 mailto URI, as defined by [RFC2368].
219
220 Example:
221 ``mailto:`` is in front of the address.
222
223 .. code-block:: text
224
225 mailto:jane_doe@example.com
226
227 Parsing:
228
229 .. code-block:: pycon
230
231 >>> from icalendar import vCalAddress
232 >>> cal_address = vCalAddress.from_ical('mailto:jane_doe@example.com')
233 >>> cal_address
234 vCalAddress('mailto:jane_doe@example.com')
235
236 Encoding:
237
238 .. code-block:: pycon
239
240 >>> from icalendar import vCalAddress, Event
241 >>> event = Event()
242 >>> jane = vCalAddress("mailto:jane_doe@example.com")
243 >>> jane.name = "Jane"
244 >>> event["organizer"] = jane
245 >>> print(event.to_ical().decode().replace('\\r\\n', '\\n').strip())
246 BEGIN:VEVENT
247 ORGANIZER;CN=Jane:mailto:jane_doe@example.com
248 END:VEVENT
249 """
250
251 params: Parameters
252 __slots__ = ("params",)
253
254 def __new__(
255 cls,
256 value,
257 encoding=DEFAULT_ENCODING,
258 /,
259 params: dict[str, Any] | None = None,
260 ):
261 value = to_unicode(value, encoding=encoding)
262 self = super().__new__(cls, value)
263 self.params = Parameters(params)
264 return self
265
266 def __repr__(self):
267 return f"vCalAddress('{self}')"
268
269 def to_ical(self):
270 return self.encode(DEFAULT_ENCODING)
271
272 @classmethod
273 def from_ical(cls, ical):
274 return cls(ical)
275
276 @property
277 def email(self) -> str:
278 """The email address without mailto: at the start."""
279 if self.lower().startswith("mailto:"):
280 return self[7:]
281 return str(self)
282
283 from icalendar.param import (
284 CN,
285 CUTYPE,
286 DELEGATED_FROM,
287 DELEGATED_TO,
288 DIR,
289 LANGUAGE,
290 PARTSTAT,
291 ROLE,
292 RSVP,
293 SENT_BY,
294 )
295
296 name = CN
297
298 @staticmethod
299 def _get_email(email: str) -> str:
300 """Extract email and add mailto: prefix if needed.
301
302 Handles case-insensitive mailto: prefix checking.
303
304 Args:
305 email: Email string that may or may not have mailto: prefix
306
307 Returns:
308 Email string with mailto: prefix
309 """
310 if not email.lower().startswith("mailto:"):
311 return f"mailto:{email}"
312 return email
313
314 @classmethod
315 def new(
316 cls,
317 email: str,
318 /,
319 cn: str | None = None,
320 cutype: str | None = None,
321 delegated_from: str | None = None,
322 delegated_to: str | None = None,
323 directory: str | None = None,
324 language: str | None = None,
325 partstat: str | None = None,
326 role: str | None = None,
327 rsvp: bool | None = None,
328 sent_by: str | None = None,
329 ):
330 """Create a new vCalAddress with RFC 5545 parameters.
331
332 Creates a vCalAddress instance with automatic mailto: prefix handling
333 and support for all standard RFC 5545 parameters.
334
335 Args:
336 email: The email address (mailto: prefix added automatically if missing)
337 cn: Common Name parameter
338 cutype: Calendar user type (INDIVIDUAL, GROUP, RESOURCE, ROOM)
339 delegated_from: Email of the calendar user that delegated
340 delegated_to: Email of the calendar user that was delegated to
341 directory: Reference to directory information
342 language: Language for text values
343 partstat: Participation status (NEEDS-ACTION, ACCEPTED, DECLINED, etc.)
344 role: Role (REQ-PARTICIPANT, OPT-PARTICIPANT, NON-PARTICIPANT, CHAIR)
345 rsvp: Whether RSVP is requested
346 sent_by: Email of the calendar user acting on behalf of this user
347
348 Returns:
349 vCalAddress: A new calendar address with specified parameters
350
351 Raises:
352 TypeError: If email is not a string
353
354 Examples:
355 Basic usage:
356
357 >>> from icalendar.prop import vCalAddress
358 >>> addr = vCalAddress.new("test@test.com")
359 >>> str(addr)
360 'mailto:test@test.com'
361
362 With parameters:
363
364 >>> addr = vCalAddress.new("test@test.com", cn="Test User", role="CHAIR")
365 >>> addr.params["CN"]
366 'Test User'
367 >>> addr.params["ROLE"]
368 'CHAIR'
369 """
370 if not isinstance(email, str):
371 raise TypeError(f"Email must be a string, not {type(email).__name__}")
372
373 # Handle mailto: prefix (case-insensitive)
374 email_with_prefix = cls._get_email(email)
375
376 # Create the address
377 addr = cls(email_with_prefix)
378
379 # Set parameters if provided
380 if cn is not None:
381 addr.params["CN"] = cn
382 if cutype is not None:
383 addr.params["CUTYPE"] = cutype
384 if delegated_from is not None:
385 addr.params["DELEGATED-FROM"] = cls._get_email(delegated_from)
386 if delegated_to is not None:
387 addr.params["DELEGATED-TO"] = cls._get_email(delegated_to)
388 if directory is not None:
389 addr.params["DIR"] = directory
390 if language is not None:
391 addr.params["LANGUAGE"] = language
392 if partstat is not None:
393 addr.params["PARTSTAT"] = partstat
394 if role is not None:
395 addr.params["ROLE"] = role
396 if rsvp is not None:
397 addr.params["RSVP"] = "TRUE" if rsvp else "FALSE"
398 if sent_by is not None:
399 addr.params["SENT-BY"] = cls._get_email(sent_by)
400
401 return addr
402
403
404class vFloat(float):
405 """Float
406
407 Value Name:
408 FLOAT
409
410 Purpose:
411 This value type is used to identify properties that contain
412 a real-number value.
413
414 Format Definition:
415 This value type is defined by the following notation:
416
417 .. code-block:: text
418
419 float = (["+"] / "-") 1*DIGIT ["." 1*DIGIT]
420
421 Description:
422 If the property permits, multiple "float" values are
423 specified by a COMMA-separated list of values.
424
425 Example:
426
427 .. code-block:: text
428
429 1000000.0000001
430 1.333
431 -3.14
432
433 .. code-block:: pycon
434
435 >>> from icalendar.prop import vFloat
436 >>> float = vFloat.from_ical('1000000.0000001')
437 >>> float
438 1000000.0000001
439 >>> float = vFloat.from_ical('1.333')
440 >>> float
441 1.333
442 >>> float = vFloat.from_ical('+1.333')
443 >>> float
444 1.333
445 >>> float = vFloat.from_ical('-3.14')
446 >>> float
447 -3.14
448 """
449
450 params: Parameters
451
452 def __new__(cls, *args, params: dict[str, Any] | None = None, **kwargs):
453 self = super().__new__(cls, *args, **kwargs)
454 self.params = Parameters(params)
455 return self
456
457 def to_ical(self):
458 return str(self).encode("utf-8")
459
460 @classmethod
461 def from_ical(cls, ical):
462 try:
463 return cls(ical)
464 except Exception as e:
465 raise ValueError(f"Expected float value, got: {ical}") from e
466
467
468class vInt(int):
469 """Integer
470
471 Value Name:
472 INTEGER
473
474 Purpose:
475 This value type is used to identify properties that contain a
476 signed integer value.
477
478 Format Definition:
479 This value type is defined by the following notation:
480
481 .. code-block:: text
482
483 integer = (["+"] / "-") 1*DIGIT
484
485 Description:
486 If the property permits, multiple "integer" values are
487 specified by a COMMA-separated list of values. The valid range
488 for "integer" is -2147483648 to 2147483647. If the sign is not
489 specified, then the value is assumed to be positive.
490
491 Example:
492
493 .. code-block:: text
494
495 1234567890
496 -1234567890
497 +1234567890
498 432109876
499
500 .. code-block:: pycon
501
502 >>> from icalendar.prop import vInt
503 >>> integer = vInt.from_ical('1234567890')
504 >>> integer
505 1234567890
506 >>> integer = vInt.from_ical('-1234567890')
507 >>> integer
508 -1234567890
509 >>> integer = vInt.from_ical('+1234567890')
510 >>> integer
511 1234567890
512 >>> integer = vInt.from_ical('432109876')
513 >>> integer
514 432109876
515 """
516
517 params: Parameters
518
519 def __new__(cls, *args, params: dict[str, Any] | None = None, **kwargs):
520 self = super().__new__(cls, *args, **kwargs)
521 self.params = Parameters(params)
522 return self
523
524 def to_ical(self) -> bytes:
525 return str(self).encode("utf-8")
526
527 @classmethod
528 def from_ical(cls, ical: ICAL_TYPE):
529 try:
530 return cls(ical)
531 except Exception as e:
532 raise ValueError(f"Expected int, got: {ical}") from e
533
534
535class vDDDLists:
536 """A list of vDDDTypes values."""
537
538 params: Parameters
539 dts: list
540
541 def __init__(self, dt_list):
542 if not hasattr(dt_list, "__iter__"):
543 dt_list = [dt_list]
544 vddd = []
545 tzid = None
546 for dt_l in dt_list:
547 dt = vDDDTypes(dt_l)
548 vddd.append(dt)
549 if "TZID" in dt.params:
550 tzid = dt.params["TZID"]
551
552 params = {}
553 if tzid:
554 # NOTE: no support for multiple timezones here!
555 params["TZID"] = tzid
556 self.params = Parameters(params)
557 self.dts = vddd
558
559 def to_ical(self):
560 dts_ical = (from_unicode(dt.to_ical()) for dt in self.dts)
561 return b",".join(dts_ical)
562
563 @staticmethod
564 def from_ical(ical, timezone=None):
565 out = []
566 ical_dates = ical.split(",")
567 for ical_dt in ical_dates:
568 out.append(vDDDTypes.from_ical(ical_dt, timezone=timezone))
569 return out
570
571 def __eq__(self, other):
572 if isinstance(other, vDDDLists):
573 return self.dts == other.dts
574 if isinstance(other, (TimeBase, date)):
575 return self.dts == [other]
576 return False
577
578 def __repr__(self):
579 """String representation."""
580 return f"{self.__class__.__name__}({self.dts})"
581
582
583class vCategory:
584 params: Parameters
585
586 def __init__(
587 self, c_list: list[str] | str, /, params: dict[str, Any] | None = None
588 ):
589 if not hasattr(c_list, "__iter__") or isinstance(c_list, str):
590 c_list = [c_list]
591 self.cats: list[vText | str] = [vText(c) for c in c_list]
592 self.params = Parameters(params)
593
594 def __iter__(self):
595 return iter(vCategory.from_ical(self.to_ical()))
596
597 def to_ical(self):
598 return b",".join(
599 [
600 c.to_ical() if hasattr(c, "to_ical") else vText(c).to_ical()
601 for c in self.cats
602 ]
603 )
604
605 @staticmethod
606 def from_ical(ical):
607 ical = to_unicode(ical)
608 return unescape_char(ical).split(",")
609
610 def __eq__(self, other):
611 """self == other"""
612 return isinstance(other, vCategory) and self.cats == other.cats
613
614 def __repr__(self):
615 """String representation."""
616 return f"{self.__class__.__name__}({self.cats}, params={self.params})"
617
618
619class TimeBase:
620 """Make classes with a datetime/date comparable."""
621
622 params: Parameters
623 ignore_for_equality = {"TZID", "VALUE"}
624
625 def __eq__(self, other):
626 """self == other"""
627 if isinstance(other, date):
628 return self.dt == other
629 if isinstance(other, TimeBase):
630 default = object()
631 for key in (
632 set(self.params) | set(other.params)
633 ) - self.ignore_for_equality:
634 if key[:2].lower() != "x-" and self.params.get(
635 key, default
636 ) != other.params.get(key, default):
637 return False
638 return self.dt == other.dt
639 if isinstance(other, vDDDLists):
640 return other == self
641 return False
642
643 def __hash__(self):
644 return hash(self.dt)
645
646 from icalendar.param import RANGE, RELATED, TZID
647
648 def __repr__(self):
649 """String representation."""
650 return f"{self.__class__.__name__}({self.dt}, {self.params})"
651
652
653class vDDDTypes(TimeBase):
654 """A combined Datetime, Date or Duration parser/generator. Their format
655 cannot be confused, and often values can be of either types.
656 So this is practical.
657 """
658
659 params: Parameters
660
661 def __init__(self, dt):
662 if not isinstance(dt, (datetime, date, timedelta, time, tuple)):
663 raise TypeError(
664 "You must use datetime, date, timedelta, time or tuple (for periods)"
665 )
666 if isinstance(dt, (datetime, timedelta)):
667 self.params = Parameters()
668 elif isinstance(dt, date):
669 self.params = Parameters({"value": "DATE"})
670 elif isinstance(dt, time):
671 self.params = Parameters({"value": "TIME"})
672 else: # isinstance(dt, tuple)
673 self.params = Parameters({"value": "PERIOD"})
674
675 tzid = tzid_from_dt(dt) if isinstance(dt, (datetime, time)) else None
676 if tzid is not None and tzid != "UTC":
677 self.params.update({"TZID": tzid})
678
679 self.dt = dt
680
681 def to_ical(self):
682 dt = self.dt
683 if isinstance(dt, datetime):
684 return vDatetime(dt).to_ical()
685 if isinstance(dt, date):
686 return vDate(dt).to_ical()
687 if isinstance(dt, timedelta):
688 return vDuration(dt).to_ical()
689 if isinstance(dt, time):
690 return vTime(dt).to_ical()
691 if isinstance(dt, tuple) and len(dt) == 2:
692 return vPeriod(dt).to_ical()
693 raise ValueError(f"Unknown date type: {type(dt)}")
694
695 @classmethod
696 def from_ical(cls, ical, timezone=None):
697 if isinstance(ical, cls):
698 return ical.dt
699 u = ical.upper()
700 if u.startswith(("P", "-P", "+P")):
701 return vDuration.from_ical(ical)
702 if "/" in u:
703 return vPeriod.from_ical(ical, timezone=timezone)
704
705 if len(ical) in (15, 16):
706 return vDatetime.from_ical(ical, timezone=timezone)
707 if len(ical) == 8:
708 if timezone:
709 tzinfo = tzp.timezone(timezone)
710 if tzinfo is not None:
711 return to_datetime(vDate.from_ical(ical)).replace(tzinfo=tzinfo)
712 return vDate.from_ical(ical)
713 if len(ical) in (6, 7):
714 return vTime.from_ical(ical)
715 raise ValueError(f"Expected datetime, date, or time. Got: '{ical}'")
716
717 @property
718 def td(self) -> timedelta:
719 """Compatibility property returning ``self.dt``.
720
721 This class is used to replace different time components.
722 Some of them contain a datetime or date (``.dt``).
723 Some of them contain a timedelta (``.td``).
724 This property allows interoperability.
725 """
726 return self.dt
727
728
729class vDate(TimeBase):
730 """Date
731
732 Value Name:
733 DATE
734
735 Purpose:
736 This value type is used to identify values that contain a
737 calendar date.
738
739 Format Definition:
740 This value type is defined by the following notation:
741
742 .. code-block:: text
743
744 date = date-value
745
746 date-value = date-fullyear date-month date-mday
747 date-fullyear = 4DIGIT
748 date-month = 2DIGIT ;01-12
749 date-mday = 2DIGIT ;01-28, 01-29, 01-30, 01-31
750 ;based on month/year
751
752 Description:
753 If the property permits, multiple "date" values are
754 specified as a COMMA-separated list of values. The format for the
755 value type is based on the [ISO.8601.2004] complete
756 representation, basic format for a calendar date. The textual
757 format specifies a four-digit year, two-digit month, and two-digit
758 day of the month. There are no separator characters between the
759 year, month, and day component text.
760
761 Example:
762 The following represents July 14, 1997:
763
764 .. code-block:: text
765
766 19970714
767
768 .. code-block:: pycon
769
770 >>> from icalendar.prop import vDate
771 >>> date = vDate.from_ical('19970714')
772 >>> date.year
773 1997
774 >>> date.month
775 7
776 >>> date.day
777 14
778 """
779
780 params: Parameters
781
782 def __init__(self, dt):
783 if not isinstance(dt, date):
784 raise TypeError("Value MUST be a date instance")
785 self.dt = dt
786 self.params = Parameters({"value": "DATE"})
787
788 def to_ical(self):
789 s = f"{self.dt.year:04}{self.dt.month:02}{self.dt.day:02}"
790 return s.encode("utf-8")
791
792 @staticmethod
793 def from_ical(ical):
794 try:
795 timetuple = (
796 int(ical[:4]), # year
797 int(ical[4:6]), # month
798 int(ical[6:8]), # day
799 )
800 return date(*timetuple)
801 except Exception as e:
802 raise ValueError(f"Wrong date format {ical}") from e
803
804
805class vDatetime(TimeBase):
806 """Date-Time
807
808 Value Name:
809 DATE-TIME
810
811 Purpose:
812 This value type is used to identify values that specify a
813 precise calendar date and time of day. The format is based on
814 the ISO.8601.2004 complete representation.
815
816 Format Definition:
817 This value type is defined by the following notation:
818
819 .. code-block:: text
820
821 date-time = date "T" time
822
823 date = date-value
824 date-value = date-fullyear date-month date-mday
825 date-fullyear = 4DIGIT
826 date-month = 2DIGIT ;01-12
827 date-mday = 2DIGIT ;01-28, 01-29, 01-30, 01-31
828 ;based on month/year
829 time = time-hour time-minute time-second [time-utc]
830 time-hour = 2DIGIT ;00-23
831 time-minute = 2DIGIT ;00-59
832 time-second = 2DIGIT ;00-60
833 time-utc = "Z"
834
835 The following is the representation of the date-time format.
836
837 .. code-block:: text
838
839 YYYYMMDDTHHMMSS
840
841 Description:
842 vDatetime is timezone aware and uses a timezone library.
843 When a vDatetime object is created from an
844 ical string, you can pass a valid timezone identifier. When a
845 vDatetime object is created from a Python :py:mod:`datetime` object, it uses the
846 tzinfo component, if present. Otherwise a timezone-naive object is
847 created. Be aware that there are certain limitations with timezone naive
848 DATE-TIME components in the icalendar standard.
849
850 Example:
851 The following represents March 2, 2021 at 10:15 AM with local time:
852
853 .. code-block:: pycon
854
855 >>> from icalendar import vDatetime
856 >>> datetime = vDatetime.from_ical("20210302T101500")
857 >>> datetime.tzname()
858 >>> datetime.year
859 2021
860 >>> datetime.minute
861 15
862
863 The following represents March 2, 2021 at 10:15 AM in New York:
864
865 .. code-block:: pycon
866
867 >>> datetime = vDatetime.from_ical("20210302T101500", 'America/New_York')
868 >>> datetime.tzname()
869 'EST'
870
871 The following represents March 2, 2021 at 10:15 AM in Berlin:
872
873 .. code-block:: pycon
874
875 >>> from zoneinfo import ZoneInfo
876 >>> timezone = ZoneInfo("Europe/Berlin")
877 >>> vDatetime.from_ical("20210302T101500", timezone)
878 datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='Europe/Berlin'))
879 """
880
881 params: Parameters
882
883 def __init__(self, dt, /, params: dict[str, Any] | None = None):
884 self.dt = dt
885 self.params = Parameters(params)
886
887 def to_ical(self):
888 dt = self.dt
889 tzid = tzid_from_dt(dt)
890
891 s = (
892 f"{dt.year:04}{dt.month:02}{dt.day:02}"
893 f"T{dt.hour:02}{dt.minute:02}{dt.second:02}"
894 )
895 if tzid == "UTC":
896 s += "Z"
897 elif tzid:
898 self.params.update({"TZID": tzid})
899 return s.encode("utf-8")
900
901 @staticmethod
902 def from_ical(ical, timezone=None):
903 """Create a datetime from the RFC string."""
904 tzinfo = None
905 if isinstance(timezone, str):
906 tzinfo = tzp.timezone(timezone)
907 elif timezone is not None:
908 tzinfo = timezone
909
910 try:
911 timetuple = (
912 int(ical[:4]), # year
913 int(ical[4:6]), # month
914 int(ical[6:8]), # day
915 int(ical[9:11]), # hour
916 int(ical[11:13]), # minute
917 int(ical[13:15]), # second
918 )
919 if tzinfo:
920 return tzp.localize(datetime(*timetuple), tzinfo) # noqa: DTZ001
921 if not ical[15:]:
922 return datetime(*timetuple) # noqa: DTZ001
923 if ical[15:16] == "Z":
924 return tzp.localize_utc(datetime(*timetuple)) # noqa: DTZ001
925 except Exception as e:
926 raise ValueError(f"Wrong datetime format: {ical}") from e
927 raise ValueError(f"Wrong datetime format: {ical}")
928
929
930class vDuration(TimeBase):
931 """Duration
932
933 Value Name:
934 DURATION
935
936 Purpose:
937 This value type is used to identify properties that contain
938 a duration of time.
939
940 Format Definition:
941 This value type is defined by the following notation:
942
943 .. code-block:: text
944
945 dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
946
947 dur-date = dur-day [dur-time]
948 dur-time = "T" (dur-hour / dur-minute / dur-second)
949 dur-week = 1*DIGIT "W"
950 dur-hour = 1*DIGIT "H" [dur-minute]
951 dur-minute = 1*DIGIT "M" [dur-second]
952 dur-second = 1*DIGIT "S"
953 dur-day = 1*DIGIT "D"
954
955 Description:
956 If the property permits, multiple "duration" values are
957 specified by a COMMA-separated list of values. The format is
958 based on the [ISO.8601.2004] complete representation basic format
959 with designators for the duration of time. The format can
960 represent nominal durations (weeks and days) and accurate
961 durations (hours, minutes, and seconds). Note that unlike
962 [ISO.8601.2004], this value type doesn't support the "Y" and "M"
963 designators to specify durations in terms of years and months.
964 The duration of a week or a day depends on its position in the
965 calendar. In the case of discontinuities in the time scale, such
966 as the change from standard time to daylight time and back, the
967 computation of the exact duration requires the subtraction or
968 addition of the change of duration of the discontinuity. Leap
969 seconds MUST NOT be considered when computing an exact duration.
970 When computing an exact duration, the greatest order time
971 components MUST be added first, that is, the number of days MUST
972 be added first, followed by the number of hours, number of
973 minutes, and number of seconds.
974
975 Example:
976 A duration of 15 days, 5 hours, and 20 seconds would be:
977
978 .. code-block:: text
979
980 P15DT5H0M20S
981
982 A duration of 7 weeks would be:
983
984 .. code-block:: text
985
986 P7W
987
988 .. code-block:: pycon
989
990 >>> from icalendar.prop import vDuration
991 >>> duration = vDuration.from_ical('P15DT5H0M20S')
992 >>> duration
993 datetime.timedelta(days=15, seconds=18020)
994 >>> duration = vDuration.from_ical('P7W')
995 >>> duration
996 datetime.timedelta(days=49)
997 """
998
999 params: Parameters
1000
1001 def __init__(self, td: timedelta | str, /, params: dict[str, Any] | None = None):
1002 if isinstance(td, str):
1003 td = vDuration.from_ical(td)
1004 if not isinstance(td, timedelta):
1005 raise TypeError("Value MUST be a timedelta instance")
1006 self.td = td
1007 self.params = Parameters(params)
1008
1009 def to_ical(self):
1010 sign = ""
1011 td = self.td
1012 if td.days < 0:
1013 sign = "-"
1014 td = -td
1015 timepart = ""
1016 if td.seconds:
1017 timepart = "T"
1018 hours = td.seconds // 3600
1019 minutes = td.seconds % 3600 // 60
1020 seconds = td.seconds % 60
1021 if hours:
1022 timepart += f"{hours}H"
1023 if minutes or (hours and seconds):
1024 timepart += f"{minutes}M"
1025 if seconds:
1026 timepart += f"{seconds}S"
1027 if td.days == 0 and timepart:
1028 return str(sign).encode("utf-8") + b"P" + str(timepart).encode("utf-8")
1029 return (
1030 str(sign).encode("utf-8")
1031 + b"P"
1032 + str(abs(td.days)).encode("utf-8")
1033 + b"D"
1034 + str(timepart).encode("utf-8")
1035 )
1036
1037 @staticmethod
1038 def from_ical(ical):
1039 match = DURATION_REGEX.match(ical)
1040 if not match:
1041 raise InvalidCalendar(f"Invalid iCalendar duration: {ical}")
1042
1043 sign, weeks, days, hours, minutes, seconds = match.groups()
1044 value = timedelta(
1045 weeks=int(weeks or 0),
1046 days=int(days or 0),
1047 hours=int(hours or 0),
1048 minutes=int(minutes or 0),
1049 seconds=int(seconds or 0),
1050 )
1051
1052 if sign == "-":
1053 value = -value
1054
1055 return value
1056
1057 @property
1058 def dt(self) -> timedelta:
1059 """The time delta for compatibility."""
1060 return self.td
1061
1062
1063class vPeriod(TimeBase):
1064 """Period of Time
1065
1066 Value Name:
1067 PERIOD
1068
1069 Purpose:
1070 This value type is used to identify values that contain a
1071 precise period of time.
1072
1073 Format Definition:
1074 This value type is defined by the following notation:
1075
1076 .. code-block:: text
1077
1078 period = period-explicit / period-start
1079
1080 period-explicit = date-time "/" date-time
1081 ; [ISO.8601.2004] complete representation basic format for a
1082 ; period of time consisting of a start and end. The start MUST
1083 ; be before the end.
1084
1085 period-start = date-time "/" dur-value
1086 ; [ISO.8601.2004] complete representation basic format for a
1087 ; period of time consisting of a start and positive duration
1088 ; of time.
1089
1090 Description:
1091 If the property permits, multiple "period" values are
1092 specified by a COMMA-separated list of values. There are two
1093 forms of a period of time. First, a period of time is identified
1094 by its start and its end. This format is based on the
1095 [ISO.8601.2004] complete representation, basic format for "DATE-
1096 TIME" start of the period, followed by a SOLIDUS character
1097 followed by the "DATE-TIME" of the end of the period. The start
1098 of the period MUST be before the end of the period. Second, a
1099 period of time can also be defined by a start and a positive
1100 duration of time. The format is based on the [ISO.8601.2004]
1101 complete representation, basic format for the "DATE-TIME" start of
1102 the period, followed by a SOLIDUS character, followed by the
1103 [ISO.8601.2004] basic format for "DURATION" of the period.
1104
1105 Example:
1106 The period starting at 18:00:00 UTC, on January 1, 1997 and
1107 ending at 07:00:00 UTC on January 2, 1997 would be:
1108
1109 .. code-block:: text
1110
1111 19970101T180000Z/19970102T070000Z
1112
1113 The period start at 18:00:00 on January 1, 1997 and lasting 5 hours
1114 and 30 minutes would be:
1115
1116 .. code-block:: text
1117
1118 19970101T180000Z/PT5H30M
1119
1120 .. code-block:: pycon
1121
1122 >>> from icalendar.prop import vPeriod
1123 >>> period = vPeriod.from_ical('19970101T180000Z/19970102T070000Z')
1124 >>> period = vPeriod.from_ical('19970101T180000Z/PT5H30M')
1125 """
1126
1127 params: Parameters
1128
1129 def __init__(self, per: tuple[datetime, Union[datetime, timedelta]]):
1130 start, end_or_duration = per
1131 if not (isinstance(start, (datetime, date))):
1132 raise TypeError("Start value MUST be a datetime or date instance")
1133 if not (isinstance(end_or_duration, (datetime, date, timedelta))):
1134 raise TypeError(
1135 "end_or_duration MUST be a datetime, date or timedelta instance"
1136 )
1137 by_duration = 0
1138 if isinstance(end_or_duration, timedelta):
1139 by_duration = 1
1140 duration = end_or_duration
1141 end = start + duration
1142 else:
1143 end = end_or_duration
1144 duration = end - start
1145 if start > end:
1146 raise ValueError("Start time is greater than end time")
1147
1148 self.params = Parameters({"value": "PERIOD"})
1149 # set the timezone identifier
1150 # does not support different timezones for start and end
1151 tzid = tzid_from_dt(start)
1152 if tzid:
1153 self.params["TZID"] = tzid
1154
1155 self.start = start
1156 self.end = end
1157 self.by_duration = by_duration
1158 self.duration = duration
1159
1160 def overlaps(self, other):
1161 if self.start > other.start:
1162 return other.overlaps(self)
1163 return self.start <= other.start < self.end
1164
1165 def to_ical(self):
1166 if self.by_duration:
1167 return (
1168 vDatetime(self.start).to_ical()
1169 + b"/"
1170 + vDuration(self.duration).to_ical()
1171 )
1172 return vDatetime(self.start).to_ical() + b"/" + vDatetime(self.end).to_ical()
1173
1174 @staticmethod
1175 def from_ical(ical, timezone=None):
1176 try:
1177 start, end_or_duration = ical.split("/")
1178 start = vDDDTypes.from_ical(start, timezone=timezone)
1179 end_or_duration = vDDDTypes.from_ical(end_or_duration, timezone=timezone)
1180 except Exception as e:
1181 raise ValueError(f"Expected period format, got: {ical}") from e
1182 return (start, end_or_duration)
1183
1184 def __repr__(self):
1185 p = (self.start, self.duration) if self.by_duration else (self.start, self.end)
1186 return f"vPeriod({p!r})"
1187
1188 @property
1189 def dt(self):
1190 """Make this cooperate with the other vDDDTypes."""
1191 return (self.start, (self.duration if self.by_duration else self.end))
1192
1193 from icalendar.param import FBTYPE
1194
1195
1196class vWeekday(str):
1197 """Either a ``weekday`` or a ``weekdaynum``
1198
1199 .. code-block:: pycon
1200
1201 >>> from icalendar import vWeekday
1202 >>> vWeekday("MO") # Simple weekday
1203 'MO'
1204 >>> vWeekday("2FR").relative # Second friday
1205 2
1206 >>> vWeekday("2FR").weekday
1207 'FR'
1208 >>> vWeekday("-1SU").relative # Last Sunday
1209 -1
1210
1211 Definition from `RFC 5545, Section 3.3.10 <https://www.rfc-editor.org/rfc/rfc5545#section-3.3.10>`_:
1212
1213 .. code-block:: text
1214
1215 weekdaynum = [[plus / minus] ordwk] weekday
1216 plus = "+"
1217 minus = "-"
1218 ordwk = 1*2DIGIT ;1 to 53
1219 weekday = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA"
1220 ;Corresponding to SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
1221 ;FRIDAY, and SATURDAY days of the week.
1222
1223 """
1224
1225 params: Parameters
1226 __slots__ = ("params", "relative", "weekday")
1227
1228 week_days = CaselessDict(
1229 {
1230 "SU": 0,
1231 "MO": 1,
1232 "TU": 2,
1233 "WE": 3,
1234 "TH": 4,
1235 "FR": 5,
1236 "SA": 6,
1237 }
1238 )
1239
1240 def __new__(
1241 cls,
1242 value,
1243 encoding=DEFAULT_ENCODING,
1244 /,
1245 params: dict[str, Any] | None = None,
1246 ):
1247 value = to_unicode(value, encoding=encoding)
1248 self = super().__new__(cls, value)
1249 match = WEEKDAY_RULE.match(self)
1250 if match is None:
1251 raise ValueError(f"Expected weekday abbrevation, got: {self}")
1252 match = match.groupdict()
1253 sign = match["signal"]
1254 weekday = match["weekday"]
1255 relative = match["relative"]
1256 if weekday not in vWeekday.week_days or sign not in "+-":
1257 raise ValueError(f"Expected weekday abbrevation, got: {self}")
1258 self.weekday = weekday or None
1259 self.relative = (relative and int(relative)) or None
1260 if sign == "-" and self.relative:
1261 self.relative *= -1
1262 self.params = Parameters(params)
1263 return self
1264
1265 def to_ical(self):
1266 return self.encode(DEFAULT_ENCODING).upper()
1267
1268 @classmethod
1269 def from_ical(cls, ical):
1270 try:
1271 return cls(ical.upper())
1272 except Exception as e:
1273 raise ValueError(f"Expected weekday abbrevation, got: {ical}") from e
1274
1275
1276class vFrequency(str):
1277 """A simple class that catches illegal values."""
1278
1279 params: Parameters
1280 __slots__ = ("params",)
1281
1282 frequencies = CaselessDict(
1283 {
1284 "SECONDLY": "SECONDLY",
1285 "MINUTELY": "MINUTELY",
1286 "HOURLY": "HOURLY",
1287 "DAILY": "DAILY",
1288 "WEEKLY": "WEEKLY",
1289 "MONTHLY": "MONTHLY",
1290 "YEARLY": "YEARLY",
1291 }
1292 )
1293
1294 def __new__(
1295 cls,
1296 value,
1297 encoding=DEFAULT_ENCODING,
1298 /,
1299 params: dict[str, Any] | None = None,
1300 ):
1301 value = to_unicode(value, encoding=encoding)
1302 self = super().__new__(cls, value)
1303 if self not in vFrequency.frequencies:
1304 raise ValueError(f"Expected frequency, got: {self}")
1305 self.params = Parameters(params)
1306 return self
1307
1308 def to_ical(self):
1309 return self.encode(DEFAULT_ENCODING).upper()
1310
1311 @classmethod
1312 def from_ical(cls, ical):
1313 try:
1314 return cls(ical.upper())
1315 except Exception as e:
1316 raise ValueError(f"Expected frequency, got: {ical}") from e
1317
1318
1319class vMonth(int):
1320 """The number of the month for recurrence.
1321
1322 In :rfc:`5545`, this is just an int.
1323 In :rfc:`7529`, this can be followed by `L` to indicate a leap month.
1324
1325 .. code-block:: pycon
1326
1327 >>> from icalendar import vMonth
1328 >>> vMonth(1) # first month January
1329 vMonth('1')
1330 >>> vMonth("5L") # leap month in Hebrew calendar
1331 vMonth('5L')
1332 >>> vMonth(1).leap
1333 False
1334 >>> vMonth("5L").leap
1335 True
1336
1337 Definition from RFC:
1338
1339 .. code-block:: text
1340
1341 type-bymonth = element bymonth {
1342 xsd:positiveInteger |
1343 xsd:string
1344 }
1345 """
1346
1347 params: Parameters
1348
1349 def __new__(cls, month: Union[str, int], /, params: dict[str, Any] | None = None):
1350 if isinstance(month, vMonth):
1351 return cls(month.to_ical().decode())
1352 if isinstance(month, str):
1353 if month.isdigit():
1354 month_index = int(month)
1355 leap = False
1356 else:
1357 if month[-1] != "L" and month[:-1].isdigit():
1358 raise ValueError(f"Invalid month: {month!r}")
1359 month_index = int(month[:-1])
1360 leap = True
1361 else:
1362 leap = False
1363 month_index = int(month)
1364 self = super().__new__(cls, month_index)
1365 self.leap = leap
1366 self.params = Parameters(params)
1367 return self
1368
1369 def to_ical(self) -> bytes:
1370 """The ical representation."""
1371 return str(self).encode("utf-8")
1372
1373 @classmethod
1374 def from_ical(cls, ical: str):
1375 return cls(ical)
1376
1377 @property
1378 def leap(self) -> bool:
1379 """Whether this is a leap month."""
1380 return self._leap
1381
1382 @leap.setter
1383 def leap(self, value: bool) -> None:
1384 self._leap = value
1385
1386 def __repr__(self) -> str:
1387 """repr(self)"""
1388 return f"{self.__class__.__name__}({str(self)!r})"
1389
1390 def __str__(self) -> str:
1391 """str(self)"""
1392 return f"{int(self)}{'L' if self.leap else ''}"
1393
1394
1395class vSkip(vText, Enum):
1396 """Skip values for RRULE.
1397
1398 These are defined in :rfc:`7529`.
1399
1400 OMIT is the default value.
1401
1402 Examples:
1403
1404 .. code-block:: pycon
1405
1406 >>> from icalendar import vSkip
1407 >>> vSkip.OMIT
1408 vSkip('OMIT')
1409 >>> vSkip.FORWARD
1410 vSkip('FORWARD')
1411 >>> vSkip.BACKWARD
1412 vSkip('BACKWARD')
1413 """
1414
1415 OMIT = "OMIT"
1416 FORWARD = "FORWARD"
1417 BACKWARD = "BACKWARD"
1418
1419 __reduce_ex__ = Enum.__reduce_ex__
1420
1421 def __repr__(self):
1422 return f"{self.__class__.__name__}({self._name_!r})"
1423
1424
1425class vRecur(CaselessDict):
1426 """Recurrence definition.
1427
1428 Property Name:
1429 RRULE
1430
1431 Purpose:
1432 This property defines a rule or repeating pattern for recurring events, to-dos,
1433 journal entries, or time zone definitions.
1434
1435 Value Type:
1436 RECUR
1437
1438 Property Parameters:
1439 IANA and non-standard property parameters can be specified on this property.
1440
1441 Conformance:
1442 This property can be specified in recurring "VEVENT", "VTODO", and "VJOURNAL"
1443 calendar components as well as in the "STANDARD" and "DAYLIGHT" sub-components
1444 of the "VTIMEZONE" calendar component, but it SHOULD NOT be specified more than once.
1445 The recurrence set generated with multiple "RRULE" properties is undefined.
1446
1447 Description:
1448 The recurrence rule, if specified, is used in computing the recurrence set.
1449 The recurrence set is the complete set of recurrence instances for a calendar component.
1450 The recurrence set is generated by considering the initial "DTSTART" property along
1451 with the "RRULE", "RDATE", and "EXDATE" properties contained within the
1452 recurring component. The "DTSTART" property defines the first instance in the
1453 recurrence set. The "DTSTART" property value SHOULD be synchronized with the
1454 recurrence rule, if specified. The recurrence set generated with a "DTSTART" property
1455 value not synchronized with the recurrence rule is undefined.
1456 The final recurrence set is generated by gathering all of the start DATE-TIME
1457 values generated by any of the specified "RRULE" and "RDATE" properties, and then
1458 excluding any start DATE-TIME values specified by "EXDATE" properties.
1459 This implies that start DATE- TIME values specified by "EXDATE" properties take
1460 precedence over those specified by inclusion properties (i.e., "RDATE" and "RRULE").
1461 Where duplicate instances are generated by the "RRULE" and "RDATE" properties,
1462 only one recurrence is considered. Duplicate instances are ignored.
1463
1464 The "DTSTART" property specified within the iCalendar object defines the first
1465 instance of the recurrence. In most cases, a "DTSTART" property of DATE-TIME value
1466 type used with a recurrence rule, should be specified as a date with local time
1467 and time zone reference to make sure all the recurrence instances start at the
1468 same local time regardless of time zone changes.
1469
1470 If the duration of the recurring component is specified with the "DTEND" or
1471 "DUE" property, then the same exact duration will apply to all the members of the
1472 generated recurrence set. Else, if the duration of the recurring component is
1473 specified with the "DURATION" property, then the same nominal duration will apply
1474 to all the members of the generated recurrence set and the exact duration of each
1475 recurrence instance will depend on its specific start time. For example, recurrence
1476 instances of a nominal duration of one day will have an exact duration of more or less
1477 than 24 hours on a day where a time zone shift occurs. The duration of a specific
1478 recurrence may be modified in an exception component or simply by using an
1479 "RDATE" property of PERIOD value type.
1480
1481 Examples:
1482 The following RRULE specifies daily events for 10 occurrences.
1483
1484 .. code-block:: text
1485
1486 RRULE:FREQ=DAILY;COUNT=10
1487
1488 Below, we parse the RRULE ical string.
1489
1490 .. code-block:: pycon
1491
1492 >>> from icalendar.prop import vRecur
1493 >>> rrule = vRecur.from_ical('FREQ=DAILY;COUNT=10')
1494 >>> rrule
1495 vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})
1496
1497 You can choose to add an rrule to an :class:`icalendar.cal.Event` or
1498 :class:`icalendar.cal.Todo`.
1499
1500 .. code-block:: pycon
1501
1502 >>> from icalendar import Event
1503 >>> event = Event()
1504 >>> event.add('RRULE', 'FREQ=DAILY;COUNT=10')
1505 >>> event.rrules
1506 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
1507 """ # noqa: E501
1508
1509 params: Parameters
1510
1511 frequencies = [
1512 "SECONDLY",
1513 "MINUTELY",
1514 "HOURLY",
1515 "DAILY",
1516 "WEEKLY",
1517 "MONTHLY",
1518 "YEARLY",
1519 ]
1520
1521 # Mac iCal ignores RRULEs where FREQ is not the first rule part.
1522 # Sorts parts according to the order listed in RFC 5545, section 3.3.10.
1523 canonical_order = (
1524 "RSCALE",
1525 "FREQ",
1526 "UNTIL",
1527 "COUNT",
1528 "INTERVAL",
1529 "BYSECOND",
1530 "BYMINUTE",
1531 "BYHOUR",
1532 "BYDAY",
1533 "BYWEEKDAY",
1534 "BYMONTHDAY",
1535 "BYYEARDAY",
1536 "BYWEEKNO",
1537 "BYMONTH",
1538 "BYSETPOS",
1539 "WKST",
1540 "SKIP",
1541 )
1542
1543 types = CaselessDict(
1544 {
1545 "COUNT": vInt,
1546 "INTERVAL": vInt,
1547 "BYSECOND": vInt,
1548 "BYMINUTE": vInt,
1549 "BYHOUR": vInt,
1550 "BYWEEKNO": vInt,
1551 "BYMONTHDAY": vInt,
1552 "BYYEARDAY": vInt,
1553 "BYMONTH": vMonth,
1554 "UNTIL": vDDDTypes,
1555 "BYSETPOS": vInt,
1556 "WKST": vWeekday,
1557 "BYDAY": vWeekday,
1558 "FREQ": vFrequency,
1559 "BYWEEKDAY": vWeekday,
1560 "SKIP": vSkip,
1561 }
1562 )
1563
1564 def __init__(self, *args, params: dict[str, Any] | None = None, **kwargs):
1565 if args and isinstance(args[0], str):
1566 # we have a string as an argument.
1567 args = (self.from_ical(args[0]),) + args[1:]
1568 for k, v in kwargs.items():
1569 if not isinstance(v, SEQUENCE_TYPES):
1570 kwargs[k] = [v]
1571 super().__init__(*args, **kwargs)
1572 self.params = Parameters(params)
1573
1574 def to_ical(self):
1575 result = []
1576 for key, vals in self.sorted_items():
1577 typ = self.types.get(key, vText)
1578 if not isinstance(vals, SEQUENCE_TYPES):
1579 vals = [vals] # noqa: PLW2901
1580 param_vals = b",".join(typ(val).to_ical() for val in vals)
1581
1582 # CaselessDict keys are always unicode
1583 param_key = key.encode(DEFAULT_ENCODING)
1584 result.append(param_key + b"=" + param_vals)
1585
1586 return b";".join(result)
1587
1588 @classmethod
1589 def parse_type(cls, key, values):
1590 # integers
1591 parser = cls.types.get(key, vText)
1592 return [parser.from_ical(v) for v in values.split(",")]
1593
1594 @classmethod
1595 def from_ical(cls, ical: str):
1596 if isinstance(ical, cls):
1597 return ical
1598 try:
1599 recur = cls()
1600 for pairs in ical.split(";"):
1601 try:
1602 key, vals = pairs.split("=")
1603 except ValueError:
1604 # E.g. incorrect trailing semicolon, like (issue #157):
1605 # FREQ=YEARLY;BYMONTH=11;BYDAY=1SU;
1606 continue
1607 recur[key] = cls.parse_type(key, vals)
1608 return cls(recur)
1609 except ValueError:
1610 raise
1611 except Exception as e:
1612 raise ValueError(f"Error in recurrence rule: {ical}") from e
1613
1614
1615class vTime(TimeBase):
1616 """Time
1617
1618 Value Name:
1619 TIME
1620
1621 Purpose:
1622 This value type is used to identify values that contain a
1623 time of day.
1624
1625 Format Definition:
1626 This value type is defined by the following notation:
1627
1628 .. code-block:: text
1629
1630 time = time-hour time-minute time-second [time-utc]
1631
1632 time-hour = 2DIGIT ;00-23
1633 time-minute = 2DIGIT ;00-59
1634 time-second = 2DIGIT ;00-60
1635 ;The "60" value is used to account for positive "leap" seconds.
1636
1637 time-utc = "Z"
1638
1639 Description:
1640 If the property permits, multiple "time" values are
1641 specified by a COMMA-separated list of values. No additional
1642 content value encoding (i.e., BACKSLASH character encoding, see
1643 vText) is defined for this value type.
1644
1645 The "TIME" value type is used to identify values that contain a
1646 time of day. The format is based on the [ISO.8601.2004] complete
1647 representation, basic format for a time of day. The text format
1648 consists of a two-digit, 24-hour of the day (i.e., values 00-23),
1649 two-digit minute in the hour (i.e., values 00-59), and two-digit
1650 seconds in the minute (i.e., values 00-60). The seconds value of
1651 60 MUST only be used to account for positive "leap" seconds.
1652 Fractions of a second are not supported by this format.
1653
1654 In parallel to the "DATE-TIME" definition above, the "TIME" value
1655 type expresses time values in three forms:
1656
1657 The form of time with UTC offset MUST NOT be used. For example,
1658 the following is not valid for a time value:
1659
1660 .. code-block:: text
1661
1662 230000-0800 ;Invalid time format
1663
1664 **FORM #1 LOCAL TIME**
1665
1666 The local time form is simply a time value that does not contain
1667 the UTC designator nor does it reference a time zone. For
1668 example, 11:00 PM:
1669
1670 .. code-block:: text
1671
1672 230000
1673
1674 Time values of this type are said to be "floating" and are not
1675 bound to any time zone in particular. They are used to represent
1676 the same hour, minute, and second value regardless of which time
1677 zone is currently being observed. For example, an event can be
1678 defined that indicates that an individual will be busy from 11:00
1679 AM to 1:00 PM every day, no matter which time zone the person is
1680 in. In these cases, a local time can be specified. The recipient
1681 of an iCalendar object with a property value consisting of a local
1682 time, without any relative time zone information, SHOULD interpret
1683 the value as being fixed to whatever time zone the "ATTENDEE" is
1684 in at any given moment. This means that two "Attendees", may
1685 participate in the same event at different UTC times; floating
1686 time SHOULD only be used where that is reasonable behavior.
1687
1688 In most cases, a fixed time is desired. To properly communicate a
1689 fixed time in a property value, either UTC time or local time with
1690 time zone reference MUST be specified.
1691
1692 The use of local time in a TIME value without the "TZID" property
1693 parameter is to be interpreted as floating time, regardless of the
1694 existence of "VTIMEZONE" calendar components in the iCalendar
1695 object.
1696
1697 **FORM #2: UTC TIME**
1698
1699 UTC time, or absolute time, is identified by a LATIN CAPITAL
1700 LETTER Z suffix character, the UTC designator, appended to the
1701 time value. For example, the following represents 07:00 AM UTC:
1702
1703 .. code-block:: text
1704
1705 070000Z
1706
1707 The "TZID" property parameter MUST NOT be applied to TIME
1708 properties whose time values are specified in UTC.
1709
1710 **FORM #3: LOCAL TIME AND TIME ZONE REFERENCE**
1711
1712 The local time with reference to time zone information form is
1713 identified by the use the "TZID" property parameter to reference
1714 the appropriate time zone definition.
1715
1716 Example:
1717 The following represents 8:30 AM in New York in winter,
1718 five hours behind UTC, in each of the three formats:
1719
1720 .. code-block:: text
1721
1722 083000
1723 133000Z
1724 TZID=America/New_York:083000
1725 """
1726
1727 def __init__(self, *args):
1728 if len(args) == 1:
1729 if not isinstance(args[0], (time, datetime)):
1730 raise ValueError(f"Expected a datetime.time, got: {args[0]}")
1731 self.dt = args[0]
1732 else:
1733 self.dt = time(*args)
1734 self.params = Parameters({"value": "TIME"})
1735
1736 def to_ical(self):
1737 return self.dt.strftime("%H%M%S")
1738
1739 @staticmethod
1740 def from_ical(ical):
1741 # TODO: timezone support
1742 try:
1743 timetuple = (int(ical[:2]), int(ical[2:4]), int(ical[4:6]))
1744 return time(*timetuple)
1745 except Exception as e:
1746 raise ValueError(f"Expected time, got: {ical}") from e
1747
1748
1749class vUri(str):
1750 """URI
1751
1752 Value Name:
1753 URI
1754
1755 Purpose:
1756 This value type is used to identify values that contain a
1757 uniform resource identifier (URI) type of reference to the
1758 property value.
1759
1760 Format Definition:
1761 This value type is defined by the following notation:
1762
1763 .. code-block:: text
1764
1765 uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
1766
1767 Description:
1768 This value type might be used to reference binary
1769 information, for values that are large, or otherwise undesirable
1770 to include directly in the iCalendar object.
1771
1772 Property values with this value type MUST follow the generic URI
1773 syntax defined in [RFC3986].
1774
1775 When a property parameter value is a URI value type, the URI MUST
1776 be specified as a quoted-string value.
1777
1778 Examples:
1779 The following is a URI for a network file:
1780
1781 .. code-block:: text
1782
1783 http://example.com/my-report.txt
1784
1785 .. code-block:: pycon
1786
1787 >>> from icalendar.prop import vUri
1788 >>> uri = vUri.from_ical('http://example.com/my-report.txt')
1789 >>> uri
1790 vUri('http://example.com/my-report.txt')
1791 >>> uri.uri
1792 'http://example.com/my-report.txt'
1793 """
1794
1795 params: Parameters
1796 __slots__ = ("params",)
1797
1798 def __new__(
1799 cls,
1800 value: str,
1801 encoding: str = DEFAULT_ENCODING,
1802 /,
1803 params: dict[str, Any] | None = None,
1804 ):
1805 value = to_unicode(value, encoding=encoding)
1806 self = super().__new__(cls, value)
1807 self.params = Parameters(params)
1808 return self
1809
1810 def to_ical(self):
1811 return self.encode(DEFAULT_ENCODING)
1812
1813 @classmethod
1814 def from_ical(cls, ical):
1815 try:
1816 return cls(ical)
1817 except Exception as e:
1818 raise ValueError(f"Expected , got: {ical}") from e
1819
1820 @property
1821 def ical_value(self) -> str:
1822 """The URI."""
1823 return self.uri
1824
1825 @property
1826 def uri(self) -> str:
1827 """The URI."""
1828 return str(self)
1829
1830 def __repr__(self) -> str:
1831 """repr(self)"""
1832 return f"{self.__class__.__name__}({self.uri!r})"
1833
1834 from icalendar.param import FMTTYPE, GAP, LABEL, LANGUAGE, LINKREL, RELTYPE, VALUE
1835
1836
1837class vUid(vText):
1838 """A UID of a component.
1839
1840 This is defined in :rfc:`9253`, Section 7.
1841 """
1842
1843 @classmethod
1844 def new(cls):
1845 """Create a new UID for convenience.
1846
1847 .. code-block:: pycon
1848
1849 >>> from icalendar import vUid
1850 >>> vUid.new()
1851 vUid('d755cef5-2311-46ed-a0e1-6733c9e15c63')
1852
1853 """
1854 return vUid(uuid.uuid4())
1855
1856 def __init__(self, uid: str):
1857 """Create a vUid."""
1858 super().__init__()
1859 self.params.setdefault("VALUE", "UID")
1860
1861 @property
1862 def uid(self) -> str:
1863 """The uid of this property."""
1864 return str(self)
1865
1866 @property
1867 def ical_value(self) -> str:
1868 """The uid of this property."""
1869 return self.uid
1870
1871 def __repr__(self) -> str:
1872 """repr(self)"""
1873 return f"{self.__class__.__name__}({self.uid!r})"
1874
1875 from icalendar.param import FMTTYPE, LABEL, LINKREL
1876
1877
1878class vXmlReference(vUri):
1879 """An XML-REFERENCE.
1880
1881 The associated value references an associated XML artifact and
1882 is a URI with an XPointer anchor value.
1883
1884 This is defined in :rfc:`9253`, Section 7.
1885 """
1886
1887 def __init__(self, xml_reference: str):
1888 """Create a new XML reference."""
1889 super().__init__()
1890 self.params.setdefault("VALUE", "XML-REFERENCE")
1891
1892 @property
1893 def xml_reference(self) -> str:
1894 """The XML reference URI of this property."""
1895 return self.uri
1896
1897 @property
1898 def x_pointer(self) -> str:
1899 """The XPointer of the URI.
1900
1901 The XPointer is defined in `W3C.WD-xptr-xpointer-20021219
1902 <https://www.rfc-editor.org/rfc/rfc9253.html#W3C.WD-xptr-xpointer-20021219>`_,
1903 and its use as an anchor is defined in `W3C.REC-xptr-framework-20030325
1904 <https://www.rfc-editor.org/rfc/rfc9253.html#W3C.REC-xptr-framework-20030325>`_.
1905 """
1906 from urllib.parse import unquote, urlparse
1907
1908 parsed = urlparse(self.xml_reference)
1909 fragment = unquote(parsed.fragment)
1910 if not fragment.startswith("xpointer(") or not fragment.endswith(")"):
1911 raise ValueError(f"No valid X Pointer found in {fragment!r}.")
1912 return fragment[9:-1]
1913
1914
1915class vGeo:
1916 """Geographic Position
1917
1918 Property Name:
1919 GEO
1920
1921 Purpose:
1922 This property specifies information related to the global
1923 position for the activity specified by a calendar component.
1924
1925 Value Type:
1926 FLOAT. The value MUST be two SEMICOLON-separated FLOAT values.
1927
1928 Property Parameters:
1929 IANA and non-standard property parameters can be specified on
1930 this property.
1931
1932 Conformance:
1933 This property can be specified in "VEVENT" or "VTODO"
1934 calendar components.
1935
1936 Description:
1937 This property value specifies latitude and longitude,
1938 in that order (i.e., "LAT LON" ordering). The longitude
1939 represents the location east or west of the prime meridian as a
1940 positive or negative real number, respectively. The longitude and
1941 latitude values MAY be specified up to six decimal places, which
1942 will allow for accuracy to within one meter of geographical
1943 position. Receiving applications MUST accept values of this
1944 precision and MAY truncate values of greater precision.
1945
1946 Example:
1947
1948 .. code-block:: text
1949
1950 GEO:37.386013;-122.082932
1951
1952 Parse vGeo:
1953
1954 .. code-block:: pycon
1955
1956 >>> from icalendar.prop import vGeo
1957 >>> geo = vGeo.from_ical('37.386013;-122.082932')
1958 >>> geo
1959 (37.386013, -122.082932)
1960
1961 Add a geo location to an event:
1962
1963 .. code-block:: pycon
1964
1965 >>> from icalendar import Event
1966 >>> event = Event()
1967 >>> latitude = 37.386013
1968 >>> longitude = -122.082932
1969 >>> event.add('GEO', (latitude, longitude))
1970 >>> event['GEO']
1971 vGeo((37.386013, -122.082932))
1972 """
1973
1974 params: Parameters
1975
1976 def __init__(
1977 self,
1978 geo: tuple[float | str | int, float | str | int],
1979 /,
1980 params: dict[str, Any] | None = None,
1981 ):
1982 """Create a new vGeo from a tuple of (latitude, longitude).
1983
1984 Raises:
1985 ValueError: if geo is not a tuple of (latitude, longitude)
1986 """
1987 try:
1988 latitude, longitude = (geo[0], geo[1])
1989 latitude = float(latitude)
1990 longitude = float(longitude)
1991 except Exception as e:
1992 raise ValueError(
1993 "Input must be (float, float) for latitude and longitude"
1994 ) from e
1995 self.latitude = latitude
1996 self.longitude = longitude
1997 self.params = Parameters(params)
1998
1999 def to_ical(self):
2000 return f"{self.latitude};{self.longitude}"
2001
2002 @staticmethod
2003 def from_ical(ical):
2004 try:
2005 latitude, longitude = ical.split(";")
2006 return (float(latitude), float(longitude))
2007 except Exception as e:
2008 raise ValueError(f"Expected 'float;float' , got: {ical}") from e
2009
2010 def __eq__(self, other):
2011 return self.to_ical() == other.to_ical()
2012
2013 def __repr__(self):
2014 """repr(self)"""
2015 return f"{self.__class__.__name__}(({self.latitude}, {self.longitude}))"
2016
2017
2018class vUTCOffset:
2019 """UTC Offset
2020
2021 Value Name:
2022 UTC-OFFSET
2023
2024 Purpose:
2025 This value type is used to identify properties that contain
2026 an offset from UTC to local time.
2027
2028 Format Definition:
2029 This value type is defined by the following notation:
2030
2031 .. code-block:: text
2032
2033 utc-offset = time-numzone
2034
2035 time-numzone = ("+" / "-") time-hour time-minute [time-second]
2036
2037 Description:
2038 The PLUS SIGN character MUST be specified for positive
2039 UTC offsets (i.e., ahead of UTC). The HYPHEN-MINUS character MUST
2040 be specified for negative UTC offsets (i.e., behind of UTC). The
2041 value of "-0000" and "-000000" are not allowed. The time-second,
2042 if present, MUST NOT be 60; if absent, it defaults to zero.
2043
2044 Example:
2045 The following UTC offsets are given for standard time for
2046 New York (five hours behind UTC) and Geneva (one hour ahead of
2047 UTC):
2048
2049 .. code-block:: text
2050
2051 -0500
2052
2053 +0100
2054
2055 .. code-block:: pycon
2056
2057 >>> from icalendar.prop import vUTCOffset
2058 >>> utc_offset = vUTCOffset.from_ical('-0500')
2059 >>> utc_offset
2060 datetime.timedelta(days=-1, seconds=68400)
2061 >>> utc_offset = vUTCOffset.from_ical('+0100')
2062 >>> utc_offset
2063 datetime.timedelta(seconds=3600)
2064 """
2065
2066 params: Parameters
2067
2068 ignore_exceptions = False # if True, and we cannot parse this
2069
2070 # component, we will silently ignore
2071 # it, rather than let the exception
2072 # propagate upwards
2073
2074 def __init__(self, td, /, params: dict[str, Any] | None = None):
2075 if not isinstance(td, timedelta):
2076 raise TypeError("Offset value MUST be a timedelta instance")
2077 self.td = td
2078 self.params = Parameters(params)
2079
2080 def to_ical(self):
2081 if self.td < timedelta(0):
2082 sign = "-%s"
2083 td = timedelta(0) - self.td # get timedelta relative to 0
2084 else:
2085 # Google Calendar rejects '0000' but accepts '+0000'
2086 sign = "+%s"
2087 td = self.td
2088
2089 days, seconds = td.days, td.seconds
2090
2091 hours = abs(days * 24 + seconds // 3600)
2092 minutes = abs((seconds % 3600) // 60)
2093 seconds = abs(seconds % 60)
2094 if seconds:
2095 duration = f"{hours:02}{minutes:02}{seconds:02}"
2096 else:
2097 duration = f"{hours:02}{minutes:02}"
2098 return sign % duration
2099
2100 @classmethod
2101 def from_ical(cls, ical):
2102 if isinstance(ical, cls):
2103 return ical.td
2104 try:
2105 sign, hours, minutes, seconds = (
2106 ical[0:1],
2107 int(ical[1:3]),
2108 int(ical[3:5]),
2109 int(ical[5:7] or 0),
2110 )
2111 offset = timedelta(hours=hours, minutes=minutes, seconds=seconds)
2112 except Exception as e:
2113 raise ValueError(f"Expected utc offset, got: {ical}") from e
2114 if not cls.ignore_exceptions and offset >= timedelta(hours=24):
2115 raise ValueError(f"Offset must be less than 24 hours, was {ical}")
2116 if sign == "-":
2117 return -offset
2118 return offset
2119
2120 def __eq__(self, other):
2121 if not isinstance(other, vUTCOffset):
2122 return False
2123 return self.td == other.td
2124
2125 def __hash__(self):
2126 return hash(self.td)
2127
2128 def __repr__(self):
2129 return f"vUTCOffset({self.td!r})"
2130
2131
2132class vInline(str):
2133 """This is an especially dumb class that just holds raw unparsed text and
2134 has parameters. Conversion of inline values are handled by the Component
2135 class, so no further processing is needed.
2136 """
2137
2138 params: Parameters
2139 __slots__ = ("params",)
2140
2141 def __new__(
2142 cls,
2143 value,
2144 encoding=DEFAULT_ENCODING,
2145 /,
2146 params: dict[str, Any] | None = None,
2147 ):
2148 value = to_unicode(value, encoding=encoding)
2149 self = super().__new__(cls, value)
2150 self.params = Parameters(params)
2151 return self
2152
2153 def to_ical(self):
2154 return self.encode(DEFAULT_ENCODING)
2155
2156 @classmethod
2157 def from_ical(cls, ical):
2158 return cls(ical)
2159
2160
2161class TypesFactory(CaselessDict):
2162 """All Value types defined in RFC 5545 are registered in this factory
2163 class.
2164
2165 The value and parameter names don't overlap. So one factory is enough for
2166 both kinds.
2167 """
2168
2169 def __init__(self, *args, **kwargs):
2170 """Set keys to upper for initial dict"""
2171 super().__init__(*args, **kwargs)
2172 self.all_types = (
2173 vBinary,
2174 vBoolean,
2175 vCalAddress,
2176 vDDDLists,
2177 vDDDTypes,
2178 vDate,
2179 vDatetime,
2180 vDuration,
2181 vFloat,
2182 vFrequency,
2183 vGeo,
2184 vInline,
2185 vInt,
2186 vPeriod,
2187 vRecur,
2188 vText,
2189 vTime,
2190 vUTCOffset,
2191 vUri,
2192 vWeekday,
2193 vCategory,
2194 )
2195 self["binary"] = vBinary
2196 self["boolean"] = vBoolean
2197 self["cal-address"] = vCalAddress
2198 self["date"] = vDDDTypes
2199 self["date-time"] = vDDDTypes
2200 self["duration"] = vDDDTypes
2201 self["float"] = vFloat
2202 self["integer"] = vInt
2203 self["period"] = vPeriod
2204 self["recur"] = vRecur
2205 self["text"] = vText
2206 self["time"] = vTime
2207 self["uri"] = vUri
2208 self["utc-offset"] = vUTCOffset
2209 self["geo"] = vGeo
2210 self["inline"] = vInline
2211 self["date-time-list"] = vDDDLists
2212 self["categories"] = vCategory
2213 self["uid"] = vUid # RFC 9253
2214 self["xml-reference"] = vXmlReference # RFC 9253
2215
2216 #################################################
2217 # Property types
2218
2219 # These are the default types
2220 types_map = CaselessDict(
2221 {
2222 ####################################
2223 # Property value types
2224 # Calendar Properties
2225 "calscale": "text",
2226 "method": "text",
2227 "prodid": "text",
2228 "version": "text",
2229 # Descriptive Component Properties
2230 "attach": "uri",
2231 "categories": "categories",
2232 "class": "text",
2233 "comment": "text",
2234 "description": "text",
2235 "geo": "geo",
2236 "location": "text",
2237 "percent-complete": "integer",
2238 "priority": "integer",
2239 "resources": "text",
2240 "status": "text",
2241 "summary": "text",
2242 # RFC 9253
2243 # link should be uri, xml-reference or uid
2244 # uri is likely most helpful if people forget to set VALUE
2245 "link": "uri",
2246 "concept": "uri",
2247 "refid": "text",
2248 # Date and Time Component Properties
2249 "completed": "date-time",
2250 "dtend": "date-time",
2251 "due": "date-time",
2252 "dtstart": "date-time",
2253 "duration": "duration",
2254 "freebusy": "period",
2255 "transp": "text",
2256 "refresh-interval": "duration", # RFC 7986
2257 # Time Zone Component Properties
2258 "tzid": "text",
2259 "tzname": "text",
2260 "tzoffsetfrom": "utc-offset",
2261 "tzoffsetto": "utc-offset",
2262 "tzurl": "uri",
2263 # Relationship Component Properties
2264 "attendee": "cal-address",
2265 "contact": "text",
2266 "organizer": "cal-address",
2267 "recurrence-id": "date-time",
2268 "related-to": "text",
2269 "url": "uri",
2270 "conference": "uri", # RFC 7986
2271 "source": "uri",
2272 "uid": "text",
2273 # Recurrence Component Properties
2274 "exdate": "date-time-list",
2275 "exrule": "recur",
2276 "rdate": "date-time-list",
2277 "rrule": "recur",
2278 # Alarm Component Properties
2279 "action": "text",
2280 "repeat": "integer",
2281 "trigger": "duration",
2282 "acknowledged": "date-time",
2283 # Change Management Component Properties
2284 "created": "date-time",
2285 "dtstamp": "date-time",
2286 "last-modified": "date-time",
2287 "sequence": "integer",
2288 # Miscellaneous Component Properties
2289 "request-status": "text",
2290 ####################################
2291 # parameter types (luckily there is no name overlap)
2292 "altrep": "uri",
2293 "cn": "text",
2294 "cutype": "text",
2295 "delegated-from": "cal-address",
2296 "delegated-to": "cal-address",
2297 "dir": "uri",
2298 "encoding": "text",
2299 "fmttype": "text",
2300 "fbtype": "text",
2301 "language": "text",
2302 "member": "cal-address",
2303 "partstat": "text",
2304 "range": "text",
2305 "related": "text",
2306 "reltype": "text",
2307 "role": "text",
2308 "rsvp": "boolean",
2309 "sent-by": "cal-address",
2310 "value": "text",
2311 # rfc 9253 parameters
2312 "label": "text",
2313 "linkrel": "text",
2314 "gap": "duration",
2315 }
2316 )
2317
2318 def for_property(self, name, value_param: str | None = None) -> type:
2319 """Returns the type class for a property or parameter.
2320
2321 Args:
2322 name: Property or parameter name
2323 value_param: Optional ``VALUE`` parameter, for example,
2324 "DATE", "DATE-TIME", or other string.
2325
2326 Returns:
2327 The appropriate value type class
2328 """
2329 # Special case: RDATE and EXDATE always use vDDDLists to support list values
2330 # regardless of the VALUE parameter
2331 if name.upper() in ("RDATE", "EXDATE"):
2332 return self["date-time-list"]
2333
2334 # Only use VALUE parameter for known properties
2335 # that support multiple value types
2336 # (like DTSTART, DTEND, etc. which can be DATE or DATE-TIME)
2337 # For unknown/custom properties, always use the default type from types_map
2338 if value_param and name in self.types_map and value_param in self:
2339 return self[value_param]
2340 return self[self.types_map.get(name, "text")]
2341
2342 def to_ical(self, name, value):
2343 """Encodes a named value from a primitive python type to an icalendar
2344 encoded string.
2345 """
2346 type_class = self.for_property(name)
2347 return type_class(value).to_ical()
2348
2349 def from_ical(self, name, value):
2350 """Decodes a named property or parameter value from an icalendar
2351 encoded string to a primitive python type.
2352 """
2353 type_class = self.for_property(name)
2354 return type_class.from_ical(value)
2355
2356
2357__all__ = [
2358 "DURATION_REGEX",
2359 "WEEKDAY_RULE",
2360 "TimeBase",
2361 "TypesFactory",
2362 "tzid_from_dt",
2363 "tzid_from_tzinfo",
2364 "vBinary",
2365 "vBoolean",
2366 "vCalAddress",
2367 "vCategory",
2368 "vDDDLists",
2369 "vDDDTypes",
2370 "vDate",
2371 "vDatetime",
2372 "vDuration",
2373 "vFloat",
2374 "vFrequency",
2375 "vGeo",
2376 "vInline",
2377 "vInt",
2378 "vMonth",
2379 "vPeriod",
2380 "vRecur",
2381 "vSkip",
2382 "vText",
2383 "vTime",
2384 "vUTCOffset",
2385 "vUid",
2386 "vUri",
2387 "vWeekday",
2388 "vXmlReference",
2389]