Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/cal/todo.py: 50%

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

108 statements  

1""":rfc:`5545` VTODO component.""" 

2 

3from __future__ import annotations 

4 

5import uuid 

6from datetime import date, datetime, timedelta 

7from typing import TYPE_CHECKING, Optional, Sequence 

8 

9from icalendar.attr import ( 

10 X_MOZ_LASTACK_property, 

11 X_MOZ_SNOOZE_TIME_property, 

12 attendees_property, 

13 categories_property, 

14 class_property, 

15 color_property, 

16 contacts_property, 

17 create_single_property, 

18 description_property, 

19 exdates_property, 

20 location_property, 

21 organizer_property, 

22 priority_property, 

23 property_del_duration, 

24 property_doc_duration_template, 

25 property_get_duration, 

26 property_set_duration, 

27 rdates_property, 

28 rrules_property, 

29 sequence_property, 

30 status_property, 

31 summary_property, 

32 uid_property, 

33 url_property, 

34) 

35from icalendar.cal.component import Component 

36from icalendar.error import IncompleteComponent, InvalidCalendar 

37from icalendar.tools import is_date 

38 

39if TYPE_CHECKING: 

40 from icalendar.alarms import Alarms 

41 from icalendar.enums import CLASS, STATUS 

42 from icalendar.prop import vCalAddress 

43 

44 

45class Todo(Component): 

46 """ 

47 A "VTODO" calendar component is a grouping of component 

48 properties that represents an action item or assignment. For 

49 example, it can be used to represent an item of work assigned to 

50 an individual, such as "Prepare for the upcoming conference 

51 seminar on Internet Calendaring". 

52 

53 Examples: 

54 Create a new Todo: 

55 

56 >>> from icalendar import Todo 

57 >>> todo = Todo.new() 

58 >>> print(todo.to_ical()) 

59 BEGIN:VTODO 

60 DTSTAMP:20250517T080612Z 

61 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63 

62 END:VTODO 

63 

64 """ 

65 

66 name = "VTODO" 

67 

68 required = ( 

69 "UID", 

70 "DTSTAMP", 

71 ) 

72 singletons = ( 

73 "CLASS", 

74 "COLOR", 

75 "COMPLETED", 

76 "CREATED", 

77 "DESCRIPTION", 

78 "DTSTAMP", 

79 "DTSTART", 

80 "GEO", 

81 "LAST-MODIFIED", 

82 "LOCATION", 

83 "ORGANIZER", 

84 "PERCENT-COMPLETE", 

85 "PRIORITY", 

86 "RECURRENCE-ID", 

87 "SEQUENCE", 

88 "STATUS", 

89 "SUMMARY", 

90 "UID", 

91 "URL", 

92 "DUE", 

93 "DURATION", 

94 ) 

95 exclusive = ( 

96 "DUE", 

97 "DURATION", 

98 ) 

99 multiple = ( 

100 "ATTACH", 

101 "ATTENDEE", 

102 "CATEGORIES", 

103 "COMMENT", 

104 "CONTACT", 

105 "EXDATE", 

106 "RSTATUS", 

107 "RELATED", 

108 "RESOURCES", 

109 "RDATE", 

110 "RRULE", 

111 ) 

112 DTSTART = create_single_property( 

113 "DTSTART", 

114 "dt", 

115 (datetime, date), 

116 date, 

117 'The "DTSTART" property for a "VTODO" specifies the inclusive start of the Todo.', # noqa: E501 

118 ) 

119 DUE = create_single_property( 

120 "DUE", 

121 "dt", 

122 (datetime, date), 

123 date, 

124 'The "DUE" property for a "VTODO" calendar component specifies the non-inclusive end of the Todo.', # noqa: E501 

125 ) 

126 DURATION = property( 

127 property_get_duration, 

128 property_set_duration, 

129 property_del_duration, 

130 property_doc_duration_template.format(component="VTODO"), 

131 ) 

132 

133 def _get_start_end_duration(self): 

134 """Verify the calendar validity and return the right attributes.""" 

135 start = self.DTSTART 

136 end = self.DUE 

137 duration = self.DURATION 

138 if duration is not None and end is not None: 

139 raise InvalidCalendar( 

140 "Only one of DUE and DURATION may be in a VTODO, not both." 

141 ) 

142 if ( 

143 isinstance(start, date) 

144 and not isinstance(start, datetime) 

145 and duration is not None 

146 and duration.seconds != 0 

147 ): 

148 raise InvalidCalendar( 

149 "When DTSTART is a date, DURATION must be of days or weeks." 

150 ) 

151 if start is not None and end is not None and is_date(start) != is_date(end): 

152 raise InvalidCalendar( 

153 "DTSTART and DUE must be of the same type, either date or datetime." 

154 ) 

155 return start, end, duration 

156 

157 @property 

158 def start(self) -> date | datetime: 

159 """The start of the VTODO. 

160 

161 Invalid values raise an InvalidCalendar. 

162 If there is no start, we also raise an IncompleteComponent error. 

163 

164 You can get the start, end and duration of a Todo as follows: 

165 

166 >>> from datetime import datetime 

167 >>> from icalendar import Todo 

168 >>> todo = Todo() 

169 >>> todo.start = datetime(2021, 1, 1, 12) 

170 >>> todo.end = datetime(2021, 1, 1, 12, 30) # 30 minutes 

171 >>> todo.duration # 1800 seconds == 30 minutes 

172 datetime.timedelta(seconds=1800) 

173 >>> print(todo.to_ical()) 

174 BEGIN:VTODO 

175 DTSTART:20210101T120000 

176 DUE:20210101T123000 

177 END:VTODO 

178 """ 

179 start = self._get_start_end_duration()[0] 

180 if start is None: 

181 raise IncompleteComponent("No DTSTART given.") 

182 return start 

183 

184 @start.setter 

185 def start(self, start: Optional[date | datetime]): 

186 """Set the start.""" 

187 self.DTSTART = start 

188 

189 @property 

190 def end(self) -> date | datetime: 

191 """The end of the todo. 

192 

193 Invalid values raise an InvalidCalendar error. 

194 If there is no end, we also raise an IncompleteComponent error. 

195 """ 

196 start, end, duration = self._get_start_end_duration() 

197 if end is None and duration is None: 

198 if start is None: 

199 raise IncompleteComponent("No DUE or DURATION+DTSTART given.") 

200 if is_date(start): 

201 return start + timedelta(days=1) 

202 return start 

203 if duration is not None: 

204 if start is not None: 

205 return start + duration 

206 raise IncompleteComponent("No DUE or DURATION+DTSTART given.") 

207 return end 

208 

209 @end.setter 

210 def end(self, end: date | datetime | None): 

211 """Set the end.""" 

212 self.DUE = end 

213 

214 @property 

215 def duration(self) -> timedelta: 

216 """The duration of the VTODO. 

217 

218 This duration is calculated from the start and end of the Todo. 

219 You cannot set the duration as it is unclear what happens to start and end. 

220 """ 

221 return self.end - self.start 

222 

223 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

224 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

225 

226 @property 

227 def alarms(self) -> Alarms: 

228 """Compute the alarm times for this component. 

229 

230 >>> from datetime import datetime 

231 >>> from icalendar import Todo 

232 >>> todo = Todo() # empty without alarms 

233 >>> todo.start = datetime(2024, 10, 26, 10, 21) 

234 >>> len(todo.alarms.times) 

235 0 

236 

237 Note that this only uses DTSTART and DUE, but ignores 

238 RDATE, EXDATE, and RRULE properties. 

239 """ 

240 from icalendar.alarms import Alarms 

241 

242 return Alarms(self) 

243 

244 color = color_property 

245 sequence = sequence_property 

246 categories = categories_property 

247 rdates = rdates_property 

248 exdates = exdates_property 

249 rrules = rrules_property 

250 uid = uid_property 

251 summary = summary_property 

252 description = description_property 

253 classification = class_property 

254 url = url_property 

255 organizer = organizer_property 

256 location = location_property 

257 priority = priority_property 

258 contacts = contacts_property 

259 status = status_property 

260 attendees = attendees_property 

261 

262 @classmethod 

263 def new( 

264 cls, 

265 /, 

266 attendees: Optional[list[vCalAddress]] = None, 

267 categories: Sequence[str] = (), 

268 classification: Optional[CLASS] = None, 

269 color: Optional[str] = None, 

270 comments: list[str] | str | None = None, 

271 contacts: list[str] | str | None = None, 

272 created: Optional[date] = None, 

273 description: Optional[str] = None, 

274 end: Optional[date | datetime] = None, 

275 last_modified: Optional[date] = None, 

276 location: Optional[str] = None, 

277 organizer: Optional[vCalAddress | str] = None, 

278 priority: Optional[int] = None, 

279 sequence: Optional[int] = None, 

280 stamp: Optional[date] = None, 

281 start: Optional[date | datetime] = None, 

282 status: Optional[STATUS] = None, 

283 summary: Optional[str] = None, 

284 uid: Optional[str | uuid.UUID] = None, 

285 url: Optional[str] = None, 

286 ): 

287 """Create a new TODO with all required properties. 

288 

289 This creates a new Todo in accordance with :rfc:`5545`. 

290 

291 Arguments: 

292 attendees: The :attr:`attendees` of the todo. 

293 categories: The :attr:`categories` of the todo. 

294 classification: The :attr:`classification` of the todo. 

295 color: The :attr:`color` of the todo. 

296 comments: The :attr:`Component.comments` of the todo. 

297 created: The :attr:`Component.created` of the todo. 

298 description: The :attr:`description` of the todo. 

299 end: The :attr:`end` of the todo. 

300 last_modified: The :attr:`Component.last_modified` of the todo. 

301 location: The :attr:`location` of the todo. 

302 organizer: The :attr:`organizer` of the todo. 

303 sequence: The :attr:`sequence` of the todo. 

304 stamp: The :attr:`Component.DTSTAMP` of the todo. 

305 If None, this is set to the current time. 

306 start: The :attr:`start` of the todo. 

307 status: The :attr:`status` of the todo. 

308 summary: The :attr:`summary` of the todo. 

309 uid: The :attr:`uid` of the todo. 

310 If None, this is set to a new :func:`uuid.uuid4`. 

311 url: The :attr:`url` of the todo. 

312 

313 Returns: 

314 :class:`Todo` 

315 

316 Raises: 

317 InvalidCalendar: If the content is not valid according to :rfc:`5545`. 

318 

319 .. warning:: As time progresses, we will be stricter with the validation. 

320 """ 

321 todo = super().new( 

322 stamp=stamp if stamp is not None else cls._utc_now(), 

323 created=created, 

324 last_modified=last_modified, 

325 comments=comments, 

326 ) 

327 todo.summary = summary 

328 todo.description = description 

329 todo.uid = uid if uid is not None else uuid.uuid4() 

330 todo.start = start 

331 todo.end = end 

332 todo.color = color 

333 todo.categories = categories 

334 todo.sequence = sequence 

335 todo.classification = classification 

336 todo.url = url 

337 todo.organizer = organizer 

338 todo.location = location 

339 todo.priority = priority 

340 todo.contacts = contacts 

341 todo.status = status 

342 todo.attendees = attendees 

343 if cls._validate_new: 

344 cls._validate_start_and_end(start, end) 

345 return todo 

346 

347 

348__all__ = ["Todo"]