1"""BYMONTH value type of RECUR from :rfc:`5545` and :rfc:`7529`."""
2
3from typing import Any
4
5from icalendar.compatibility import Self
6from icalendar.error import JCalParsingError
7from icalendar.parser import Parameters
8
9
10class vMonth(int):
11 """The number of the month for recurrence.
12
13 In :rfc:`5545`, this is just an int.
14 In :rfc:`7529`, this can be followed by `L` to indicate a leap month.
15
16 .. code-block:: pycon
17
18 >>> from icalendar import vMonth
19 >>> vMonth(1) # first month January
20 vMonth('1')
21 >>> vMonth("5L") # leap month in Hebrew calendar
22 vMonth('5L')
23 >>> vMonth(1).leap
24 False
25 >>> vMonth("5L").leap
26 True
27
28 Definition from RFC:
29
30 .. code-block:: text
31
32 type-bymonth = element bymonth {
33 xsd:positiveInteger |
34 xsd:string
35 }
36 """
37
38 params: Parameters
39
40 def __new__(cls, month: str | int, /, params: dict[str, Any] | None = None):
41 if isinstance(month, vMonth):
42 return cls(month.to_ical().decode())
43 if isinstance(month, str):
44 if month.isdigit():
45 month_index = int(month)
46 leap = False
47 else:
48 if not month or month[-1] != "L" or not month[:-1].isdigit():
49 raise ValueError(f"Invalid month: {month!r}")
50 month_index = int(month[:-1])
51 leap = True
52 else:
53 leap = False
54 month_index = int(month)
55 self = super().__new__(cls, month_index)
56 self.leap = leap
57 self.params = Parameters(params)
58 return self
59
60 def to_ical(self) -> bytes:
61 """The ical representation."""
62 return str(self).encode("utf-8")
63
64 @classmethod
65 def from_ical(cls, ical: str):
66 return cls(ical)
67
68 @property
69 def leap(self) -> bool:
70 """Whether this is a leap month."""
71 return self._leap
72
73 @leap.setter
74 def leap(self, value: bool) -> None:
75 self._leap = value
76
77 def __repr__(self) -> str:
78 """repr(self)"""
79 return f"{self.__class__.__name__}({str(self)!r})"
80
81 def __str__(self) -> str:
82 """str(self)"""
83 return f"{int(self)}{'L' if self.leap else ''}"
84
85 @classmethod
86 def parse_jcal_value(cls, value: Any) -> Self:
87 """Parse a jCal value for vMonth.
88
89 Raises:
90 ~error.JCalParsingError: If the value is not a valid month.
91 """
92 JCalParsingError.validate_value_type(value, (str, int), cls)
93 try:
94 return cls(value)
95 except ValueError as e:
96 raise JCalParsingError(
97 "The value must be a string or an integer.", cls, value=value
98 ) from e
99
100
101__all__ = ["vMonth"]