Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/fields/datetime.py: 31%

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

106 statements  

1import datetime 

2import warnings 

3from collections.abc import Callable 

4 

5from wtforms import widgets 

6from wtforms.fields.core import Field 

7from wtforms.utils import clean_datetime_format_for_strptime 

8 

9__all__ = ( 

10 "DateTimeField", 

11 "DateField", 

12 "TimeField", 

13 "MonthField", 

14 "DateTimeLocalField", 

15 "WeekField", 

16) 

17 

18 

19class DateTimeField(Field): 

20 """ 

21 A text field which stores a :class:`datetime.datetime` matching one or 

22 several formats. If ``format`` is a list, any input value matching any 

23 format will be accepted, and the first format in the list will be used 

24 to produce HTML values. 

25 

26 .. deprecated:: 3.3.0 

27 ``DateTimeField`` renders ``<input type="datetime">``, which is 

28 obsolete. Use :class:`DateTimeLocalField` instead. ``DateTimeField`` 

29 will be removed in WTForms 3.4. 

30 """ 

31 

32 widget = widgets.DateTimeInput() 

33 

34 def __init__( 

35 self, 

36 label=None, 

37 validators=None, 

38 format="%Y-%m-%d %H:%M:%S", 

39 invalid_value_message=None, 

40 **kwargs, 

41 ): 

42 super().__init__(label, validators, **kwargs) 

43 if type(self) is DateTimeField: 

44 warnings.warn( 

45 "'DateTimeField' renders <input type=\"datetime\">, which is" 

46 " obsolete. Use 'DateTimeLocalField' instead. 'DateTimeField'" 

47 " will be removed in WTForms 3.4.", 

48 DeprecationWarning, 

49 stacklevel=2, 

50 ) 

51 self.format = format if isinstance(format, list) else [format] 

52 self.strptime_format = clean_datetime_format_for_strptime(self.format) 

53 self.invalid_value_message = invalid_value_message 

54 

55 def _value(self): 

56 if self.raw_data: 

57 return " ".join(self.raw_data) 

58 format = self.format[0] 

59 return self.data and self.data.strftime(format) or "" 

60 

61 def process_formdata(self, valuelist): 

62 if not valuelist: 

63 return 

64 

65 for format in self.strptime_format: 

66 try: 

67 date_str = " ".join(valuelist) 

68 self.data = datetime.datetime.strptime(date_str, format) 

69 return 

70 except (ValueError, TypeError): 

71 self.data = None 

72 

73 raise ValueError( 

74 self.invalid_value_message or self.gettext("Not a valid datetime value.") 

75 ) 

76 

77 

78class DateField(DateTimeField): 

79 """ 

80 Same as :class:`~wtforms.fields.DateTimeField`, except stores a 

81 :class:`datetime.date` and renders as an :mdn-input:`date`. 

82 """ 

83 

84 widget = widgets.DateInput() 

85 

86 def __init__( 

87 self, 

88 label=None, 

89 validators=None, 

90 format="%Y-%m-%d", 

91 invalid_value_message=None, 

92 **kwargs, 

93 ): 

94 super().__init__( 

95 label, 

96 validators, 

97 format, 

98 invalid_value_message=invalid_value_message, 

99 **kwargs, 

100 ) 

101 

102 def process_formdata(self, valuelist): 

103 if not valuelist: 

104 return 

105 

106 for format in self.strptime_format: 

107 try: 

108 date_str = " ".join(valuelist) 

109 self.data = datetime.datetime.strptime(date_str, format).date() 

110 return 

111 except (ValueError, TypeError): 

112 self.data = None 

113 

114 raise ValueError( 

115 self.invalid_value_message or self.gettext("Not a valid date value.") 

116 ) 

117 

118 

119class TimeField(DateTimeField): 

120 """ 

121 Same as :class:`~wtforms.fields.DateTimeField`, except stores a 

122 :class:`datetime.time` and renders as an :mdn-input:`time`. 

123 """ 

124 

125 widget = widgets.TimeInput() 

126 

127 def __init__( 

128 self, 

129 label=None, 

130 validators=None, 

131 format="%H:%M", 

132 invalid_value_message=None, 

133 **kwargs, 

134 ): 

135 super().__init__( 

136 label, 

137 validators, 

138 format, 

139 invalid_value_message=invalid_value_message, 

140 **kwargs, 

141 ) 

142 

143 def process_formdata(self, valuelist): 

144 if not valuelist: 

145 return 

146 

147 for format in self.strptime_format: 

148 try: 

149 time_str = " ".join(valuelist) 

150 self.data = datetime.datetime.strptime(time_str, format).time() 

151 return 

152 except (ValueError, TypeError): 

153 self.data = None 

154 

155 raise ValueError( 

156 self.invalid_value_message or self.gettext("Not a valid time value.") 

157 ) 

158 

159 

160class MonthField(DateField): 

161 """ 

162 Same as :class:`~wtforms.fields.DateField`, except represents a month, 

163 stores a :class:`datetime.date` with `day = 1`, and renders as an 

164 :mdn-input:`month`. 

165 """ 

166 

167 widget = widgets.MonthInput() 

168 

169 def __init__(self, label=None, validators=None, format="%Y-%m", **kwargs): 

170 super().__init__(label, validators, format, **kwargs) 

171 

172 

173class WeekField(DateField): 

174 """ 

175 Same as :class:`~wtforms.fields.DateField`, except represents a week, 

176 stores a :class:`datetime.date` of the monday of the given week, and 

177 renders as an :mdn-input:`week`. 

178 """ 

179 

180 widget = widgets.WeekInput() 

181 

182 def __init__( 

183 self, 

184 label=None, 

185 validators=None, 

186 format="%Y-W%W", 

187 invalid_value_message=None, 

188 **kwargs, 

189 ): 

190 super().__init__( 

191 label, 

192 validators, 

193 format, 

194 invalid_value_message=invalid_value_message, 

195 **kwargs, 

196 ) 

197 

198 def process_formdata(self, valuelist): 

199 if not valuelist: 

200 return 

201 

202 for format in self.strptime_format: 

203 try: 

204 time_str = " ".join(valuelist) 

205 if "%w" not in format: 

206 # The '%w' week starting day is needed. This defaults it to monday 

207 # like ISO 8601 indicates. 

208 self.data = datetime.datetime.strptime( 

209 f"{time_str}-1", f"{format}-%w" 

210 ).date() 

211 else: 

212 self.data = datetime.datetime.strptime(time_str, format).date() 

213 return 

214 except (ValueError, TypeError): 

215 self.data = None 

216 

217 raise ValueError( 

218 self.invalid_value_message or self.gettext("Not a valid week value.") 

219 ) 

220 

221 

222class DateTimeLocalField(DateTimeField): 

223 """ 

224 Same as :class:`~wtforms.fields.DateTimeField`, but represents an 

225 :mdn-input:`datetime-local`. 

226 

227 :param tz: 

228 Optional timezone associated with the input. The HTML 

229 ``datetime-local`` widget always renders and submits a naive 

230 local datetime; ``tz`` declares the zone in which that local 

231 datetime should be interpreted. Accepts: 

232 

233 - ``None`` (default): legacy behavior, :attr:`data` is naive. 

234 - a :class:`datetime.tzinfo` instance: parsed values get this 

235 zone attached, and aware values rendered through the field 

236 are converted to it before being formatted. 

237 - a callable returning a :class:`datetime.tzinfo` (or ``None``): 

238 resolved on each access, useful when the zone depends on the 

239 request context (e.g. user preferences). Returning ``None`` 

240 falls back to the naive behavior. 

241 

242 No correction is applied for DST gaps or overlaps — submitted 

243 values are annotated as-is via ``replace(tzinfo=...)``. 

244 """ 

245 

246 widget = widgets.DateTimeLocalInput() 

247 

248 def __init__( 

249 self, 

250 *args, 

251 tz: datetime.tzinfo | Callable[[], datetime.tzinfo | None] | None = None, 

252 **kwargs, 

253 ): 

254 kwargs.setdefault( 

255 "format", 

256 [ 

257 "%Y-%m-%d %H:%M:%S", 

258 "%Y-%m-%dT%H:%M:%S", 

259 "%Y-%m-%d %H:%M", 

260 "%Y-%m-%dT%H:%M", 

261 ], 

262 ) 

263 super().__init__(*args, **kwargs) 

264 self.tz = tz 

265 

266 def _resolve_tz(self): 

267 return self.tz() if callable(self.tz) else self.tz 

268 

269 def _value(self): 

270 """Render :attr:`data`, converting aware values to ``tz`` and stripping 

271 the zone before formatting.""" 

272 if self.raw_data: 

273 return " ".join(self.raw_data) 

274 

275 if not self.data: 

276 return "" 

277 

278 value = self.data 

279 tz = self._resolve_tz() 

280 if tz is not None and value.tzinfo is not None: 

281 value = value.astimezone(tz).replace(tzinfo=None) 

282 

283 return value.strftime(self.format[0]) 

284 

285 def process_formdata(self, valuelist): 

286 """Parse the submitted value and annotate it with ``tz`` if set.""" 

287 super().process_formdata(valuelist) 

288 tz = self._resolve_tz() 

289 if tz is not None and self.data is not None: 

290 self.data = self.data.replace(tzinfo=tz)