Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/recur/month.py: 88%

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

48 statements  

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 # ``str.isdigit`` is True for non-ASCII digits (e.g. "١٢") that 

45 # ``int`` then either accepts as a different value or rejects, so 

46 # gate on ASCII digits to match what ``int`` parses below. 

47 if month.isascii() and month.isdigit(): 

48 month_index = int(month) 

49 leap = False 

50 else: 

51 digits = month[:-1] 

52 if ( 

53 not month 

54 or month[-1] != "L" 

55 or not (digits.isascii() and digits.isdigit()) 

56 ): 

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

58 month_index = int(digits) 

59 leap = True 

60 else: 

61 leap = False 

62 month_index = int(month) 

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

64 self.leap = leap 

65 self.params = Parameters(params) 

66 return self 

67 

68 def to_ical(self) -> bytes: 

69 """The ical representation.""" 

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

71 

72 @classmethod 

73 def from_ical(cls, ical: str): 

74 return cls(ical) 

75 

76 @property 

77 def leap(self) -> bool: 

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

79 return self._leap 

80 

81 @leap.setter 

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

83 self._leap = value 

84 

85 def __repr__(self) -> str: 

86 """repr(self)""" 

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

88 

89 def __str__(self) -> str: 

90 """str(self)""" 

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

92 

93 @classmethod 

94 def parse_jcal_value(cls, value: Any) -> Self: 

95 """Parse a jCal value for vMonth. 

96 

97 Raises: 

98 ~error.JCalParsingError: If the value is not a valid month. 

99 """ 

100 JCalParsingError.validate_value_type(value, (str, int), cls) 

101 try: 

102 return cls(value) 

103 except ValueError as e: 

104 raise JCalParsingError( 

105 "The value must be a string or an integer.", cls, value=value 

106 ) from e 

107 

108 

109__all__ = ["vMonth"]