Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dateutil/tz/_common.py: 24%

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

121 statements  

1 

2from functools import wraps 

3 

4from datetime import datetime, timedelta, tzinfo 

5 

6 

7ZERO = timedelta(0) 

8 

9__all__ = ["enfold"] 

10 

11 

12# The following is adapted from Alexander Belopolsky's tz library 

13# https://github.com/abalkin/tz 

14def enfold(dt: datetime, fold: int = 1) -> datetime: 

15 """Returns a datetime with the ``fold`` attribute set to ``fold``. 

16 

17 :param fold: 

18 The value for the ``fold`` attribute, either 0 or 1. 

19 

20 :return: 

21 A :class:`datetime.datetime` with the requested ``fold`` value. 

22 

23 .. versionadded:: 2.6.0 

24 """ 

25 return dt.replace(fold=fold) 

26 

27 

28def _validate_fromutc_inputs(f): 

29 """ 

30 The CPython version of ``fromutc`` checks that the input is a ``datetime`` 

31 object and that ``self`` is attached as its ``tzinfo``. 

32 """ 

33 @wraps(f) 

34 def fromutc(self, dt): 

35 if not isinstance(dt, datetime): 

36 raise TypeError("fromutc() requires a datetime argument") 

37 if dt.tzinfo is not self: 

38 raise ValueError("dt.tzinfo is not self") 

39 

40 return f(self, dt) 

41 

42 return fromutc 

43 

44 

45class _tzinfo(tzinfo): 

46 """ 

47 Base class for all ``dateutil`` ``tzinfo`` objects. 

48 """ 

49 

50 def is_ambiguous(self, dt): 

51 """ 

52 Whether or not the "wall time" of a given datetime is ambiguous in this 

53 zone. 

54 

55 :param dt: 

56 A :py:class:`datetime.datetime`, naive or time zone aware. 

57 

58 

59 :return: 

60 Returns ``True`` if ambiguous, ``False`` otherwise. 

61 

62 .. versionadded:: 2.6.0 

63 """ 

64 

65 dt = dt.replace(tzinfo=self) 

66 

67 wall_0 = enfold(dt, fold=0) 

68 wall_1 = enfold(dt, fold=1) 

69 

70 same_offset = wall_0.utcoffset() == wall_1.utcoffset() 

71 same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None) 

72 

73 return same_dt and not same_offset 

74 

75 def _fold_status(self, dt_utc, dt_wall): 

76 """ 

77 Determine the fold status of a "wall" datetime, given a representation 

78 of the same datetime as a (naive) UTC datetime. This is calculated based 

79 on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all 

80 datetimes, and that this offset is the actual number of hours separating 

81 ``dt_utc`` and ``dt_wall``. 

82 

83 :param dt_utc: 

84 Representation of the datetime as UTC 

85 

86 :param dt_wall: 

87 Representation of the datetime as "wall time". This parameter must 

88 either have a `fold` attribute or have a fold-naive 

89 :class:`datetime.tzinfo` attached, otherwise the calculation may 

90 fail. 

91 """ 

92 if self.is_ambiguous(dt_wall): 

93 delta_wall = dt_wall - dt_utc 

94 _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst())) 

95 else: 

96 _fold = 0 

97 

98 return _fold 

99 

100 def _fromutc(self, dt): 

101 """ 

102 Given a timezone-aware datetime in a given timezone, calculates a 

103 timezone-aware datetime in a new timezone. 

104 

105 Since this is the one time that we *know* we have an unambiguous 

106 datetime object, we take this opportunity to determine whether the 

107 datetime is ambiguous and in a "fold" state (e.g. if it's the first 

108 occurrence, chronologically, of the ambiguous datetime). 

109 

110 :param dt: 

111 A timezone-aware :class:`datetime.datetime` object. 

112 """ 

113 

114 # Re-implement the algorithm from Python's datetime.py 

115 dtoff = dt.utcoffset() 

116 if dtoff is None: 

117 raise ValueError("fromutc() requires a non-None utcoffset() " 

118 "result") 

119 

120 # The original datetime.py code assumes that `dst()` defaults to 

121 # zero during ambiguous times. PEP 495 inverts this presumption, so 

122 # select fold=1 when calculating the DST adjustment below. 

123 dtdst = dt.dst() 

124 if dtdst is None: 

125 raise ValueError("fromutc() requires a non-None dst() result") 

126 delta = dtoff - dtdst 

127 

128 dt += delta 

129 # Set fold=1 so we can default to being in the fold for 

130 # ambiguous dates. 

131 dtdst = enfold(dt, fold=1).dst() 

132 if dtdst is None: 

133 raise ValueError("fromutc(): dt.dst gave inconsistent " 

134 "results; cannot convert") 

135 return dt + dtdst 

136 

137 @_validate_fromutc_inputs 

138 def fromutc(self, dt): 

139 """ 

140 Given a timezone-aware datetime in a given timezone, calculates a 

141 timezone-aware datetime in a new timezone. 

142 

143 Since this is the one time that we *know* we have an unambiguous 

144 datetime object, we take this opportunity to determine whether the 

145 datetime is ambiguous and in a "fold" state (e.g. if it's the first 

146 occurrence, chronologically, of the ambiguous datetime). 

147 

148 :param dt: 

149 A timezone-aware :class:`datetime.datetime` object. 

150 """ 

151 dt_wall = self._fromutc(dt) 

152 

153 # Calculate the fold status given the two datetimes. 

154 _fold = self._fold_status(dt, dt_wall) 

155 

156 # Set the default fold value for ambiguous dates 

157 return enfold(dt_wall, fold=_fold) 

158 

159 

160class tzrangebase(_tzinfo): 

161 """ 

162 This is an abstract base class for time zones represented by an annual 

163 transition into and out of DST. Child classes should implement the following 

164 methods: 

165 

166 * ``__init__(self, *args, **kwargs)`` 

167 * ``transitions(self, year)`` - this is expected to return a tuple of 

168 datetimes representing the DST on and off transitions in standard 

169 time. 

170 

171 A fully initialized ``tzrangebase`` subclass should also provide the 

172 following attributes: 

173 * ``hasdst``: Boolean whether or not the zone uses DST. 

174 * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects 

175 representing the respective UTC offsets. 

176 * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short 

177 abbreviations in DST and STD, respectively. 

178 * ``_hasdst``: Whether or not the zone has DST. 

179 

180 .. versionadded:: 2.6.0 

181 """ 

182 def __init__(self): 

183 raise NotImplementedError('tzrangebase is an abstract base class') 

184 

185 def utcoffset(self, dt): 

186 isdst = self._isdst(dt) 

187 

188 if isdst is None: 

189 return None 

190 elif isdst: 

191 return self._dst_offset 

192 else: 

193 return self._std_offset 

194 

195 def dst(self, dt): 

196 isdst = self._isdst(dt) 

197 

198 if isdst is None: 

199 return None 

200 elif isdst: 

201 return self._dst_base_offset 

202 else: 

203 return ZERO 

204 

205 def tzname(self, dt): 

206 if self._isdst(dt): 

207 return self._dst_abbr 

208 else: 

209 return self._std_abbr 

210 

211 def fromutc(self, dt): 

212 """ Given a datetime in UTC, return local time """ 

213 if not isinstance(dt, datetime): 

214 raise TypeError("fromutc() requires a datetime argument") 

215 

216 if dt.tzinfo is not self: 

217 raise ValueError("dt.tzinfo is not self") 

218 

219 # Get transitions - if there are none, fixed offset 

220 transitions = self.transitions(dt.year) 

221 if transitions is None: 

222 return dt + self.utcoffset(dt) 

223 

224 # Get the transition times in UTC 

225 dston, dstoff = transitions 

226 

227 dston -= self._std_offset 

228 dstoff -= self._std_offset 

229 

230 utc_transitions = (dston, dstoff) 

231 dt_utc = dt.replace(tzinfo=None) 

232 

233 isdst = self._naive_isdst(dt_utc, utc_transitions) 

234 

235 if isdst: 

236 dt_wall = dt + self._dst_offset 

237 else: 

238 dt_wall = dt + self._std_offset 

239 

240 _fold = int(not isdst and self.is_ambiguous(dt_wall)) 

241 

242 return enfold(dt_wall, fold=_fold) 

243 

244 def is_ambiguous(self, dt): 

245 """ 

246 Whether or not the "wall time" of a given datetime is ambiguous in this 

247 zone. 

248 

249 :param dt: 

250 A :py:class:`datetime.datetime`, naive or time zone aware. 

251 

252 

253 :return: 

254 Returns ``True`` if ambiguous, ``False`` otherwise. 

255 

256 .. versionadded:: 2.6.0 

257 """ 

258 if not self.hasdst: 

259 return False 

260 

261 start, end = self.transitions(dt.year) 

262 

263 dt = dt.replace(tzinfo=None) 

264 return (end <= dt < end + self._dst_base_offset) 

265 

266 def _isdst(self, dt): 

267 if not self.hasdst: 

268 return False 

269 elif dt is None: 

270 return None 

271 

272 transitions = self.transitions(dt.year) 

273 

274 if transitions is None: 

275 return False 

276 

277 dt = dt.replace(tzinfo=None) 

278 

279 isdst = self._naive_isdst(dt, transitions) 

280 

281 # Handle ambiguous dates 

282 if not isdst and self.is_ambiguous(dt): 

283 return not dt.fold 

284 else: 

285 return isdst 

286 

287 def _naive_isdst(self, dt, transitions): 

288 dston, dstoff = transitions 

289 

290 dt = dt.replace(tzinfo=None) 

291 

292 if dston < dstoff: 

293 isdst = dston <= dt < dstoff 

294 else: 

295 isdst = not dstoff <= dt < dston 

296 

297 return isdst 

298 

299 @property 

300 def _dst_base_offset(self): 

301 return self._dst_offset - self._std_offset 

302 

303 __hash__ = None 

304 

305 def __repr__(self): 

306 return "%s(...)" % self.__class__.__name__ 

307 

308 __reduce__ = object.__reduce__