1"""FREQ value type of RECUR from :rfc:`5545`."""
2
3from __future__ import annotations
4
5from typing import TYPE_CHECKING, Any
6from xml.etree.ElementTree import Element, SubElement
7
8from icalendar.caselessdict import CaselessDict
9from icalendar.error import JCalParsingError, XCalParsingError
10from icalendar.parser import Parameters
11from icalendar.parser.xcal.wrapper import from_xcal_wrapper
12from icalendar.parser_tools import DEFAULT_ENCODING, to_unicode
13
14if TYPE_CHECKING:
15 from icalendar.compatibility import Self
16 from icalendar.parser.xcal.base import XCalParser
17
18
19class vFrequency(str):
20 """A simple class that catches illegal values."""
21
22 params: Parameters
23 __slots__ = ("params",)
24
25 frequencies = CaselessDict(
26 {
27 "SECONDLY": "SECONDLY",
28 "MINUTELY": "MINUTELY",
29 "HOURLY": "HOURLY",
30 "DAILY": "DAILY",
31 "WEEKLY": "WEEKLY",
32 "MONTHLY": "MONTHLY",
33 "YEARLY": "YEARLY",
34 }
35 )
36
37 def __new__(
38 cls,
39 value,
40 encoding=DEFAULT_ENCODING,
41 /,
42 params: dict[str, Any] | None = None,
43 ):
44 value = to_unicode(value, encoding=encoding)
45 self = super().__new__(cls, value)
46 if self not in vFrequency.frequencies:
47 raise ValueError(f"Expected frequency, got: {self}")
48 self.params = Parameters(params)
49 return self
50
51 def to_ical(self):
52 return self.encode(DEFAULT_ENCODING).upper()
53
54 @classmethod
55 def from_ical(cls, ical):
56 try:
57 return cls(ical.upper())
58 except Exception as e:
59 raise ValueError(f"Expected frequency, got: {ical}") from e
60
61 @classmethod
62 def parse_jcal_value(cls, value: Any) -> Self:
63 """Parse a jCal value for vFrequency.
64
65 Raises:
66 ~error.JCalParsingError: If the value is not a valid frequency.
67 """
68 JCalParsingError.validate_value_type(value, str, cls)
69 try:
70 return cls(value)
71 except ValueError as e:
72 raise JCalParsingError(
73 "The value must be a valid frequency.", cls, value=value
74 ) from e
75
76 @property
77 def ical_value(self) -> str:
78 """Returns the frequency value as a string, for example, ``WEEKLY`` or ``DAILY``.
79
80 See Also:
81
82 :rfc:`5545#section-3.3.10` for the ``FREQ`` rule grammar.
83 """
84 return str(self)
85
86 @classmethod
87 @from_xcal_wrapper
88 def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self:
89 """Parse xCal from :rfc:`6321`.
90
91 Parameters:
92 xml: The XML to parse or a parser.
93
94 Raises:
95 ~error.XCalParsingError: If the provided xCal is invalid.
96 """
97 child = parser.parse_tag()
98 text = child.get_xsd_token()
99 try:
100 return cls(text, params=params)
101 except ValueError as e:
102 raise XCalParsingError(
103 "Expected frequency https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10",
104 text,
105 child,
106 ) from e
107
108 def to_xcal(self, element: Element) -> None:
109 """Add the xCal representation of this property according to :rfc:`6321`."""
110 self.params.to_xcal(element)
111 frequency_element = SubElement(element, "freq")
112 frequency_element.text = str(self)
113
114
115__all__ = ["vFrequency"]