Source code for icalendar.prop.recur.month
"""BYMONTH value type of RECUR from :rfc:`5545` and :rfc:`7529`."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from xml.etree.ElementTree import Element, SubElement
from icalendar.error import JCalParsingError, XCalParsingError
from icalendar.parser import Parameters
from icalendar.parser.xcal.wrapper import from_xcal_wrapper
if TYPE_CHECKING:
from icalendar.compatibility import Self
from icalendar.parser.xcal.base import XCalParser
[docs]
class vMonth(int):
"""The number of the month for recurrence.
In :rfc:`5545`, this is just an int.
In :rfc:`7529`, this can be followed by `L` to indicate a leap month.
.. code-block:: pycon
>>> from icalendar import vMonth
>>> vMonth(1) # first month January
vMonth('1')
>>> vMonth("5L") # leap month in Hebrew calendar
vMonth('5L')
>>> vMonth(1).leap
False
>>> vMonth("5L").leap
True
Definition from RFC:
.. code-block:: text
type-bymonth = element bymonth {
xsd:positiveInteger |
xsd:string
}
"""
params: Parameters
def __new__(cls, month: str | int, /, params: dict[str, Any] | None = None):
if isinstance(month, vMonth):
return cls(month.to_ical().decode())
if isinstance(month, str):
# ``str.isdigit`` is True for non-ASCII digits (e.g. "١٢") that
# ``int`` then either accepts as a different value or rejects, so
# gate on ASCII digits to match what ``int`` parses below.
if month.isascii() and month.isdigit():
month_index = int(month)
leap = False
else:
digits = month[:-1]
if (
not month
or month[-1] != "L"
or not (digits.isascii() and digits.isdigit())
):
raise ValueError(f"Invalid month: {month!r}")
month_index = int(digits)
leap = True
else:
leap = False
month_index = int(month)
self = super().__new__(cls, month_index)
self.leap = leap
self.params = Parameters(params)
return self
[docs]
def to_ical(self) -> bytes:
"""The ical representation."""
return str(self).encode("utf-8")
[docs]
@classmethod
def from_ical(cls, ical: str):
return cls(ical)
@property
def leap(self) -> bool:
"""Whether this is a leap month."""
return self._leap
@leap.setter
def leap(self, value: bool) -> None:
self._leap = value
def __repr__(self) -> str:
"""repr(self)"""
return f"{self.__class__.__name__}({str(self)!r})"
def __str__(self) -> str:
"""str(self)"""
return f"{int(self)}{'L' if self.leap else ''}"
[docs]
@classmethod
def parse_jcal_value(cls, value: Any) -> Self:
"""Parse a jCal value for vMonth.
Raises:
~icalendar.error.JCalParsingError: If the value is not a valid month.
"""
JCalParsingError.validate_value_type(value, (str, int), cls)
try:
return cls(value)
except ValueError as e:
raise JCalParsingError(
"The value must be a string or an integer.", cls, value=value
) from e
[docs]
@classmethod
@from_xcal_wrapper
def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self:
"""Parse xCal from :rfc:`6321`.
Parameters:
xml: The XML to parse or a parser.
Raises:
~error.XCalParsingError: If the provided xCal is invalid.
"""
child = parser.parse_tag()
text = child.get_xsd_token()
try:
return cls(text, params=params)
except ValueError as e:
raise XCalParsingError("Expected month number", text, child) from e
[docs]
def to_xcal(self, element: Element) -> None:
"""Add the xCal representation of this property according to :rfc:`6321`."""
self.params.to_xcal(element)
month_element = SubElement(element, "bymonth")
month_element.text = str(self)
__all__ = ["vMonth"]