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

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

79 statements  

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

2 

3from datetime import datetime 

4from typing import Any, ClassVar 

5 

6from icalendar.compatibility import Self 

7from icalendar.error import JCalParsingError 

8from icalendar.parser import Parameters 

9from icalendar.parser_tools import to_unicode 

10from icalendar.timezone import tzp 

11from icalendar.timezone.tzid import is_utc 

12 

13from .base import TimeBase 

14 

15 

16class vDatetime(TimeBase): 

17 """Date-Time 

18 

19 Value Name: 

20 DATE-TIME 

21 

22 Purpose: 

23 This value type is used to identify values that specify a 

24 precise calendar date and time of day. The format is based on 

25 the ISO.8601.2004 complete representation. 

26 

27 Format Definition: 

28 This value type is defined by the following notation: 

29 

30 .. code-block:: text 

31 

32 date-time = date "T" time 

33 

34 date = date-value 

35 date-value = date-fullyear date-month date-mday 

36 date-fullyear = 4DIGIT 

37 date-month = 2DIGIT ;01-12 

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

39 ;based on month/year 

40 time = time-hour time-minute time-second [time-utc] 

41 time-hour = 2DIGIT ;00-23 

42 time-minute = 2DIGIT ;00-59 

43 time-second = 2DIGIT ;00-60 

44 time-utc = "Z" 

45 

46 The following is the representation of the date-time format. 

47 

48 .. code-block:: text 

49 

50 YYYYMMDDTHHMMSS 

51 

52 Description: 

53 vDatetime is timezone aware and uses a timezone library. 

54 When a vDatetime object is created from an 

55 ical string, you can pass a valid timezone identifier. When a 

56 vDatetime object is created from a Python :mod:`datetime` object, it uses the 

57 tzinfo component, if present. Otherwise a timezone-naive object is 

58 created. Be aware that there are certain limitations with timezone naive 

59 DATE-TIME components in the icalendar standard. 

60 

61 Example: 

62 The following represents March 2, 2021 at 10:15 AM with local time: 

63 

64 .. code-block:: pycon 

65 

66 >>> from icalendar import vDatetime 

67 >>> datetime = vDatetime.from_ical("20210302T101500") 

68 >>> datetime.tzname() 

69 >>> datetime.year 

70 2021 

71 >>> datetime.minute 

72 15 

73 

74 The following represents March 2, 2021 at 10:15 AM in New York: 

75 

76 .. code-block:: pycon 

77 

78 >>> datetime = vDatetime.from_ical("20210302T101500", 'America/New_York') 

79 >>> datetime.tzname() 

80 'EST' 

81 

82 The following represents March 2, 2021 at 10:15 AM in Berlin: 

83 

84 .. code-block:: pycon 

85 

86 >>> from zoneinfo import ZoneInfo 

87 >>> timezone = ZoneInfo("Europe/Berlin") 

88 >>> vDatetime.from_ical("20210302T101500", timezone) 

89 datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='Europe/Berlin')) 

90 """ 

91 

92 default_value: ClassVar[str] = "DATE-TIME" 

93 params: Parameters 

94 

95 def __init__(self, dt: datetime, /, params: dict[str, Any] | None = None) -> None: 

96 self.dt = dt 

97 self.params = Parameters(params) 

98 self.params.update_tzid_from(dt) 

99 

100 def to_ical(self): 

101 dt = self.dt 

102 

103 s = ( 

104 f"{dt.year:04}{dt.month:02}{dt.day:02}" 

105 f"T{dt.hour:02}{dt.minute:02}{dt.second:02}" 

106 ) 

107 if self.is_utc(): 

108 s += "Z" 

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

110 

111 @staticmethod 

112 def from_ical(ical, timezone=None): 

113 """Create a datetime from the RFC string.""" 

114 ical = to_unicode(ical) 

115 tzinfo = None 

116 if isinstance(timezone, str): 

117 tzinfo = tzp.timezone(timezone) 

118 elif timezone is not None: 

119 tzinfo = timezone 

120 

121 # Extract the value part if parameters are present per 

122 # https://datatracker.ietf.org/doc/html/rfc5545.html#section-3.3.5 

123 # Form #3: TZID=America/New_York:19980119T020000 

124 ical_value = ical.rpartition(":")[2] 

125 

126 if len(ical_value) < 15 or ical_value[8] != "T": 

127 raise ValueError(f"Wrong datetime format: {ical}") 

128 

129 try: 

130 timetuple = ( 

131 int(ical_value[:4]), # year 

132 int(ical_value[4:6]), # month 

133 int(ical_value[6:8]), # day 

134 int(ical_value[9:11]), # hour 

135 int(ical_value[11:13]), # minute 

136 int(ical_value[13:15]), # second 

137 ) 

138 if tzinfo: 

139 return tzp.localize(datetime(*timetuple), tzinfo) 

140 if not ical_value[15:]: 

141 return datetime(*timetuple) 

142 if ical_value[15:] == "Z": 

143 return tzp.localize_utc(datetime(*timetuple)) 

144 except Exception as e: 

145 raise ValueError(f"Wrong datetime format: {ical}") from e 

146 raise ValueError(f"Wrong datetime format: {ical}") 

147 

148 @classmethod 

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

150 """Examples of vDatetime.""" 

151 return [cls(datetime(2025, 11, 10, 16, 52))] 

152 

153 from icalendar.param import VALUE 

154 

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

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

157 value = self.dt.strftime("%Y-%m-%dT%H:%M:%S") 

158 if self.is_utc(): 

159 value += "Z" 

160 return [name, self.params.to_jcal(exclude_utc=True), self.VALUE.lower(), value] 

161 

162 def is_utc(self) -> bool: 

163 """Whether this datetime is UTC.""" 

164 return self.params.is_utc() or is_utc(self.dt) 

165 

166 @classmethod 

167 def parse_jcal_value(cls, jcal: str) -> datetime: 

168 """Parse a jCal string to a :class:`datetime.datetime`. 

169 

170 Raises: 

171 ~error.JCalParsingError: If it can't parse a date-time value. 

172 """ 

173 JCalParsingError.validate_value_type(jcal, str, cls) 

174 utc = jcal.endswith("Z") 

175 if utc: 

176 jcal = jcal[:-1] 

177 try: 

178 dt = datetime.strptime(jcal, "%Y-%m-%dT%H:%M:%S") 

179 except ValueError as e: 

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

181 if utc: 

182 return tzp.localize_utc(dt) 

183 return dt 

184 

185 @classmethod 

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

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

188 

189 Parameters: 

190 jcal_property: The jCal property to parse. 

191 

192 Raises: 

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

194 """ 

195 JCalParsingError.validate_property(jcal_property, cls) 

196 params = Parameters.from_jcal_property(jcal_property) 

197 with JCalParsingError.reraise_with_path_added(3): 

198 dt = cls.parse_jcal_value(jcal_property[3]) 

199 if params.tzid: 

200 dt = tzp.localize(dt, params.tzid) 

201 return cls( 

202 dt, 

203 params=params, 

204 ) 

205 

206 

207__all__ = ["vDatetime"]