Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pendulum/tz/timezone.py: 58%

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

96 statements  

1# mypy: no-warn-redundant-casts 

2from __future__ import annotations 

3 

4import datetime as _datetime 

5import zoneinfo 

6 

7from abc import ABC 

8from abc import abstractmethod 

9from typing import TYPE_CHECKING 

10from typing import TypeVar 

11from typing import cast 

12 

13from pendulum.tz.exceptions import AmbiguousTime 

14from pendulum.tz.exceptions import InvalidTimezone 

15from pendulum.tz.exceptions import NonExistingTime 

16 

17 

18if TYPE_CHECKING: 

19 from typing_extensions import Self 

20 

21POST_TRANSITION = "post" 

22PRE_TRANSITION = "pre" 

23TRANSITION_ERROR = "error" 

24 

25 

26_DT = TypeVar("_DT", bound=_datetime.datetime) 

27 

28 

29class PendulumTimezone(ABC): 

30 @property 

31 @abstractmethod 

32 def name(self) -> str: 

33 raise NotImplementedError 

34 

35 @abstractmethod 

36 def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: 

37 raise NotImplementedError 

38 

39 @abstractmethod 

40 def datetime( 

41 self, 

42 year: int, 

43 month: int, 

44 day: int, 

45 hour: int = 0, 

46 minute: int = 0, 

47 second: int = 0, 

48 microsecond: int = 0, 

49 ) -> _datetime.datetime: 

50 raise NotImplementedError 

51 

52 

53class Timezone(zoneinfo.ZoneInfo, PendulumTimezone): 

54 """ 

55 Represents a named timezone. 

56 

57 The accepted names are those provided by the IANA time zone database. 

58 

59 >>> from pendulum.tz.timezone import Timezone 

60 >>> tz = Timezone('Europe/Paris') 

61 """ 

62 

63 def __new__(cls, key: str) -> Self: 

64 try: 

65 return super().__new__(cls, key) # type: ignore[call-arg] 

66 except zoneinfo.ZoneInfoNotFoundError: 

67 raise InvalidTimezone(key) 

68 

69 def __eq__(self, other: object) -> bool: 

70 return isinstance(other, Timezone) and self.key == other.key 

71 

72 @property 

73 def name(self) -> str: 

74 return self.key 

75 

76 def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: 

77 """ 

78 Converts a datetime in the current timezone. 

79 

80 If the datetime is naive, it will be normalized. 

81 

82 >>> from datetime import datetime 

83 >>> from pendulum import timezone 

84 >>> paris = timezone('Europe/Paris') 

85 >>> dt = datetime(2013, 3, 31, 2, 30, fold=1) 

86 >>> in_paris = paris.convert(dt) 

87 >>> in_paris.isoformat() 

88 '2013-03-31T03:30:00+02:00' 

89 

90 If the datetime is aware, it will be properly converted. 

91 

92 >>> new_york = timezone('America/New_York') 

93 >>> in_new_york = new_york.convert(in_paris) 

94 >>> in_new_york.isoformat() 

95 '2013-03-30T21:30:00-04:00' 

96 """ 

97 

98 if dt.tzinfo is None: 

99 # Technically, utcoffset() can return None, but none of the zone information 

100 # in tzdata sets _tti_before to None. This can be checked with the following 

101 # code: 

102 # 

103 # >>> import zoneinfo 

104 # >>> from zoneinfo._zoneinfo import ZoneInfo 

105 # 

106 # >>> for tzname in zoneinfo.available_timezones(): 

107 # >>> if ZoneInfo(tzname)._tti_before is None: 

108 # >>> print(tzname) 

109 

110 offset_before = cast( 

111 "_datetime.timedelta", 

112 (self.utcoffset(dt.replace(fold=0)) if dt.fold else self.utcoffset(dt)), 

113 ) 

114 offset_after = cast( 

115 "_datetime.timedelta", 

116 (self.utcoffset(dt) if dt.fold else self.utcoffset(dt.replace(fold=1))), 

117 ) 

118 

119 if offset_after > offset_before: 

120 # Skipped time 

121 if raise_on_unknown_times: 

122 raise NonExistingTime(dt) 

123 

124 dt = cast( 

125 "_DT", 

126 dt 

127 + ( 

128 (offset_after - offset_before) 

129 if dt.fold 

130 else (offset_before - offset_after) 

131 ), 

132 ) 

133 elif offset_before > offset_after and raise_on_unknown_times: 

134 # Repeated time 

135 raise AmbiguousTime(dt) 

136 

137 return dt.replace(tzinfo=self) 

138 

139 return cast("_DT", dt.astimezone(self)) 

140 

141 def datetime( 

142 self, 

143 year: int, 

144 month: int, 

145 day: int, 

146 hour: int = 0, 

147 minute: int = 0, 

148 second: int = 0, 

149 microsecond: int = 0, 

150 ) -> _datetime.datetime: 

151 """ 

152 Return a normalized datetime for the current timezone. 

153 """ 

154 return self.convert( 

155 _datetime.datetime( 

156 year, month, day, hour, minute, second, microsecond, fold=1 

157 ) 

158 ) 

159 

160 def __repr__(self) -> str: 

161 return f"{self.__class__.__name__}('{self.name}')" 

162 

163 

164class FixedTimezone(_datetime.tzinfo, PendulumTimezone): 

165 def __init__(self, offset: int, name: str | None = None) -> None: 

166 sign = "-" if offset < 0 else "+" 

167 

168 minutes = offset / 60 

169 hour, minute = divmod(abs(int(minutes)), 60) 

170 

171 if not name: 

172 name = f"{sign}{hour:02d}:{minute:02d}" 

173 

174 self._name = name 

175 self._offset = offset 

176 self._utcoffset = _datetime.timedelta(seconds=offset) 

177 

178 def __eq__(self, other: object) -> bool: 

179 return isinstance(other, FixedTimezone) and self._offset == other._offset 

180 

181 @property 

182 def name(self) -> str: 

183 return self._name 

184 

185 def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT: 

186 if dt.tzinfo is None: 

187 return dt.__class__( 

188 dt.year, 

189 dt.month, 

190 dt.day, 

191 dt.hour, 

192 dt.minute, 

193 dt.second, 

194 dt.microsecond, 

195 tzinfo=self, 

196 fold=0, 

197 ) 

198 

199 return cast("_DT", dt.astimezone(self)) 

200 

201 def datetime( 

202 self, 

203 year: int, 

204 month: int, 

205 day: int, 

206 hour: int = 0, 

207 minute: int = 0, 

208 second: int = 0, 

209 microsecond: int = 0, 

210 ) -> _datetime.datetime: 

211 return self.convert( 

212 _datetime.datetime( 

213 year, month, day, hour, minute, second, microsecond, fold=1 

214 ) 

215 ) 

216 

217 @property 

218 def offset(self) -> int: 

219 return self._offset 

220 

221 def utcoffset(self, dt: _datetime.datetime | None) -> _datetime.timedelta: 

222 return self._utcoffset 

223 

224 def dst(self, dt: _datetime.datetime | None) -> _datetime.timedelta: 

225 return _datetime.timedelta() 

226 

227 def fromutc(self, dt: _datetime.datetime) -> _datetime.datetime: 

228 # Use the stdlib datetime's add method to avoid infinite recursion 

229 return (_datetime.datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self) 

230 

231 def tzname(self, dt: _datetime.datetime | None) -> str | None: 

232 return self._name 

233 

234 def __getinitargs__(self) -> tuple[int, str]: 

235 return self._offset, self._name 

236 

237 def __repr__(self) -> str: 

238 name = "" 

239 if self._name: 

240 name = f', name="{self._name}"' 

241 

242 return f"{self.__class__.__name__}({self._offset}{name})" 

243 

244 

245UTC = Timezone("UTC")