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

75 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 ICalParsingError, 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) -> bytes: 

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: str) -> date: 

92 """Parse an iCalendar DATE value. 

93 

94 Parameters: 

95 ical (str | bytes): Required. The DATE value in ``YYYYMMDD`` format. 

96 

97 Raises: 

98 ~icalendar.error.ICalParsingError: If the value is not a valid DATE. 

99 """ 

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

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

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

103 raise ICalParsingError( 

104 "Wrong date format", 

105 value=ical, 

106 ) 

107 try: 

108 timetuple = ( 

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

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

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

112 ) 

113 return date(*timetuple) 

114 except Exception as e: 

115 raise ICalParsingError( 

116 "Wrong date format", 

117 value=ical, 

118 ) from e 

119 

120 @classmethod 

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

122 """Examples of vDate.""" 

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

124 

125 from icalendar.param import VALUE 

126 

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

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

129 return [ 

130 name, 

131 self.params.to_jcal(), 

132 self.VALUE.lower(), 

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

134 ] 

135 

136 @classmethod 

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

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

139 

140 Raises: 

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

142 """ 

143 JCalParsingError.validate_value_type(jcal, str, cls) 

144 try: 

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

146 except ValueError as e: 

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

148 

149 @classmethod 

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

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

152 

153 Parameters: 

154 jcal_property: The jCal property to parse. 

155 

156 Raises: 

157 ~icalendar.error.JCalParsingError: If the provided jCal is invalid. 

158 """ 

159 JCalParsingError.validate_property(jcal_property, cls) 

160 with JCalParsingError.reraise_with_path_added(3): 

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

162 return cls( 

163 value, 

164 params=Parameters.from_jcal_property(jcal_property), 

165 ) 

166 

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

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

169 self.params.to_xcal(element) 

170 element = SubElement(element, "date") 

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

172 

173 @classmethod 

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

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

176 

177 Parameters: 

178 element: The xCal element to parse. 

179 

180 Returns: 

181 The date object. 

182 

183 Raises: 

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

185 """ 

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

187 try: 

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

189 except ValueError as e: 

190 raise XCalParsingError( 

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

192 ) from e 

193 

194 @classmethod 

195 @from_xcal_wrapper 

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

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

198 

199 Parameters: 

200 xml: The XML to parse or a parser. 

201 

202 Raises: 

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

204 """ 

205 element = parser.parse_tag(cls.default_value) 

206 d = cls.from_xcal_element(element) 

207 return cls( 

208 d, 

209 params=params, 

210 ) 

211 

212 @property 

213 def ical_value(self) -> date: 

214 """Returns the date as a date value, in year-month-day format. 

215 

216 .. seealso:: 

217 

218 :rfc:`5545#section-3.3.4` for the ``DATE`` value type. 

219 

220 """ 

221 return self.dt 

222 

223 

224__all__ = ["vDate"]