Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/dt/date.py: 64%

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

72 statements  

1"""DATE property type from :rfc:`5545`.""" 

2 

3from __future__ import annotations 

4 

5from datetime import date, datetime 

6from typing import TYPE_CHECKING, Any, ClassVar 

7from xml.etree.ElementTree import Element, SubElement 

8 

9from icalendar.error import JCalParsingError, XCalParsingError 

10from icalendar.parser import Parameters 

11from icalendar.parser.xcal.match import XCalRegexMatcher 

12from icalendar.parser.xcal.wrapper import from_xcal_wrapper 

13 

14from .base import TimeBase 

15 

16if TYPE_CHECKING: 

17 from icalendar.compatibility import Self 

18 from icalendar.parser.xcal.adapter import ElementAdapter 

19 from icalendar.parser.xcal.base import XCalParser 

20 

21XCAL_DATE_REGEX = XCalRegexMatcher( 

22 r"(\d\d\d\d)-(\d\d)-(\d\d)", "Expected date format YYYY-MM-DD" 

23) 

24 

25 

26class vDate(TimeBase): 

27 """Date 

28 

29 Value Name: 

30 DATE 

31 

32 Purpose: 

33 This value type is used to identify values that contain a 

34 calendar date. 

35 

36 Format Definition: 

37 This value type is defined by the following notation: 

38 

39 .. code-block:: text 

40 

41 date = date-value 

42 

43 date-value = date-fullyear date-month date-mday 

44 date-fullyear = 4DIGIT 

45 date-month = 2DIGIT ;01-12 

46 date-mday = 2DIGIT ;01-28, 01-29, 01-30, 01-31 

47 ;based on month/year 

48 

49 Description: 

50 If the property permits, multiple "date" values are 

51 specified as a COMMA-separated list of values. The format for the 

52 value type is based on the [ISO.8601.2004] complete 

53 representation, basic format for a calendar date. The textual 

54 format specifies a four-digit year, two-digit month, and two-digit 

55 day of the month. There are no separator characters between the 

56 year, month, and day component text. 

57 

58 Example: 

59 The following represents July 14, 1997: 

60 

61 .. code-block:: ics 

62 

63 19970714 

64 

65 .. code-block:: pycon 

66 

67 >>> from icalendar.prop import vDate 

68 >>> date = vDate.from_ical('19970714') 

69 >>> date.year 

70 1997 

71 >>> date.month 

72 7 

73 >>> date.day 

74 14 

75 """ 

76 

77 default_value: ClassVar[str] = "DATE" 

78 params: Parameters 

79 

80 def __init__(self, dt: date, params: dict[str, Any] | None = None) -> None: 

81 if not isinstance(dt, date): 

82 raise TypeError("Value MUST be a date instance") 

83 self.dt = dt 

84 self.params = Parameters(params or {}) 

85 

86 def to_ical(self): 

87 s = f"{self.dt.year:04}{self.dt.month:02}{self.dt.day:02}" 

88 return s.encode("utf-8") 

89 

90 @staticmethod 

91 def from_ical(ical): 

92 # date-value = 4DIGIT 2DIGIT 2DIGIT, no separators, 

93 # per https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.4 

94 if len(ical) != 8 or not ical.isascii() or not ical.isdigit(): 

95 raise ValueError(f"Wrong date format {ical}") 

96 try: 

97 timetuple = ( 

98 int(ical[:4]), # year 

99 int(ical[4:6]), # month 

100 int(ical[6:8]), # day 

101 ) 

102 return date(*timetuple) 

103 except Exception as e: 

104 raise ValueError(f"Wrong date format {ical}") from e 

105 

106 @classmethod 

107 def examples(cls) -> list[Self]: 

108 """Examples of vDate.""" 

109 return [cls(date(2025, 11, 10))] 

110 

111 from icalendar.param import VALUE 

112 

113 def to_jcal(self, name: str) -> list: 

114 """The jCal representation of this property according to :rfc:`7265`.""" 

115 return [ 

116 name, 

117 self.params.to_jcal(), 

118 self.VALUE.lower(), 

119 self.dt.strftime("%Y-%m-%d"), 

120 ] 

121 

122 @classmethod 

123 def parse_jcal_value(cls, jcal: str) -> date: 

124 """Parse a jCal string to a :class:`datetime.date`. 

125 

126 Raises: 

127 ~error.JCalParsingError: If it can't parse a date. 

128 """ 

129 JCalParsingError.validate_value_type(jcal, str, cls) 

130 try: 

131 return datetime.strptime(jcal, "%Y-%m-%d").date() 

132 except ValueError as e: 

133 raise JCalParsingError("Cannot parse date.", cls, value=jcal) from e 

134 

135 @classmethod 

136 def from_jcal(cls, jcal_property: list) -> Self: 

137 """Parse jCal from :rfc:`7265`. 

138 

139 Parameters: 

140 jcal_property: The jCal property to parse. 

141 

142 Raises: 

143 ~error.JCalParsingError: If the provided jCal is invalid. 

144 """ 

145 JCalParsingError.validate_property(jcal_property, cls) 

146 with JCalParsingError.reraise_with_path_added(3): 

147 value = cls.parse_jcal_value(jcal_property[3]) 

148 return cls( 

149 value, 

150 params=Parameters.from_jcal_property(jcal_property), 

151 ) 

152 

153 def to_xcal(self, element: Element) -> None: 

154 """The xCal representation of this property according to :rfc:`6321`.""" 

155 self.params.to_xcal(element) 

156 element = SubElement(element, "date") 

157 element.text = self.dt.strftime("%Y-%m-%d") 

158 

159 @classmethod 

160 def from_xcal_element(cls, element: ElementAdapter) -> date: 

161 """Parse xCal from :rfc:`6321`. 

162 

163 Parameters: 

164 element: The xCal element to parse. 

165 

166 Returns: 

167 The date object. 

168 

169 Raises: 

170 ~error.XCalParsingError: If the provided xCal is invalid. 

171 """ 

172 year, month, day = XCAL_DATE_REGEX.groups(element) 

173 try: 

174 return date(int(year), int(month), int(day)) 

175 except ValueError as e: 

176 raise XCalParsingError( 

177 "Date is out of range", element.get_xsd_token(), element 

178 ) from e 

179 

180 @classmethod 

181 @from_xcal_wrapper 

182 def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self: 

183 """Parse xCal from :rfc:`6321`. 

184 

185 Parameters: 

186 xml: The XML to parse or a parser. 

187 

188 Raises: 

189 ~error.XCalParsingError: If the provided xCal is invalid. 

190 """ 

191 element = parser.parse_tag(cls.default_value) 

192 d = cls.from_xcal_element(element) 

193 return cls( 

194 d, 

195 params=params, 

196 ) 

197 

198 

199__all__ = ["vDate"]