Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/utils/dateutil.py @ 46680:15c2f9220ae8
typing: add type annotations to mercurial/utils/dateutil.py
For now, I'm just typing around the edges to help find issues with TortoiseHg.
If the custom `hgdate` type is useful elsewhere as I go, I'll move it to a file
dedicated to custom types. I'm not loving the ban on camelcase type names here
that test-check-code.t flagged, but I'm not sure how to disable that even if
everyone agreed that it's a bad idea to go against the normal convention for
types.
While here, fix an issue that pytype found in `parsedate` when an invalid date
tuple is passed by raising a ProgrammingError instead of crashing. (Tuple
doesn't have a `strip` attribute.)
Differential Revision: https://phab.mercurial-scm.org/D10123
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Sat, 06 Mar 2021 15:26:46 -0500 |
parents | 6894c9ef4dcd |
children | 6000f5b25c9b |
comparison
equal
deleted
inserted
replaced
46679:e571fec5b606 | 46680:15c2f9220ae8 |
---|---|
15 from .. import ( | 15 from .. import ( |
16 encoding, | 16 encoding, |
17 error, | 17 error, |
18 pycompat, | 18 pycompat, |
19 ) | 19 ) |
20 | |
21 if pycompat.TYPE_CHECKING: | |
22 from typing import ( | |
23 Callable, | |
24 Dict, | |
25 Iterable, | |
26 Optional, | |
27 Tuple, | |
28 Union, | |
29 ) | |
30 | |
31 hgdate = Tuple[float, int] # (unixtime, offset) | |
20 | 32 |
21 # used by parsedate | 33 # used by parsedate |
22 defaultdateformats = ( | 34 defaultdateformats = ( |
23 b'%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601 | 35 b'%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601 |
24 b'%Y-%m-%dT%H:%M', # without seconds | 36 b'%Y-%m-%dT%H:%M', # without seconds |
60 b"%b %Y", | 72 b"%b %Y", |
61 ) | 73 ) |
62 | 74 |
63 | 75 |
64 def makedate(timestamp=None): | 76 def makedate(timestamp=None): |
77 # type: (Optional[float]) -> hgdate | |
65 """Return a unix timestamp (or the current time) as a (unixtime, | 78 """Return a unix timestamp (or the current time) as a (unixtime, |
66 offset) tuple based off the local timezone.""" | 79 offset) tuple based off the local timezone.""" |
67 if timestamp is None: | 80 if timestamp is None: |
68 timestamp = time.time() | 81 timestamp = time.time() |
69 if timestamp < 0: | 82 if timestamp < 0: |
77 tz = delta.days * 86400 + delta.seconds | 90 tz = delta.days * 86400 + delta.seconds |
78 return timestamp, tz | 91 return timestamp, tz |
79 | 92 |
80 | 93 |
81 def datestr(date=None, format=b'%a %b %d %H:%M:%S %Y %1%2'): | 94 def datestr(date=None, format=b'%a %b %d %H:%M:%S %Y %1%2'): |
95 # type: (Optional[hgdate], bytes) -> bytes | |
82 """represent a (unixtime, offset) tuple as a localized time. | 96 """represent a (unixtime, offset) tuple as a localized time. |
83 unixtime is seconds since the epoch, and offset is the time zone's | 97 unixtime is seconds since the epoch, and offset is the time zone's |
84 number of seconds away from UTC. | 98 number of seconds away from UTC. |
85 | 99 |
86 >>> datestr((0, 0)) | 100 >>> datestr((0, 0)) |
114 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format))) | 128 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format))) |
115 return s | 129 return s |
116 | 130 |
117 | 131 |
118 def shortdate(date=None): | 132 def shortdate(date=None): |
133 # type: (Optional[hgdate]) -> bytes | |
119 """turn (timestamp, tzoff) tuple into iso 8631 date.""" | 134 """turn (timestamp, tzoff) tuple into iso 8631 date.""" |
120 return datestr(date, format=b'%Y-%m-%d') | 135 return datestr(date, format=b'%Y-%m-%d') |
121 | 136 |
122 | 137 |
123 def parsetimezone(s): | 138 def parsetimezone(s): |
139 # type: (bytes) -> Tuple[Optional[int], bytes] | |
124 """find a trailing timezone, if any, in string, and return a | 140 """find a trailing timezone, if any, in string, and return a |
125 (offset, remainder) pair""" | 141 (offset, remainder) pair""" |
126 s = pycompat.bytestr(s) | 142 s = pycompat.bytestr(s) |
127 | 143 |
128 if s.endswith(b"GMT") or s.endswith(b"UTC"): | 144 if s.endswith(b"GMT") or s.endswith(b"UTC"): |
154 | 170 |
155 return None, s | 171 return None, s |
156 | 172 |
157 | 173 |
158 def strdate(string, format, defaults=None): | 174 def strdate(string, format, defaults=None): |
175 # type: (bytes, bytes, Optional[Dict[bytes, Tuple[bytes, bytes]]]) -> hgdate | |
159 """parse a localized time string and return a (unixtime, offset) tuple. | 176 """parse a localized time string and return a (unixtime, offset) tuple. |
160 if the string cannot be parsed, ValueError is raised.""" | 177 if the string cannot be parsed, ValueError is raised.""" |
161 if defaults is None: | 178 if defaults is None: |
162 defaults = {} | 179 defaults = {} |
163 | 180 |
196 unixtime = localunixtime + offset | 213 unixtime = localunixtime + offset |
197 return unixtime, offset | 214 return unixtime, offset |
198 | 215 |
199 | 216 |
200 def parsedate(date, formats=None, bias=None): | 217 def parsedate(date, formats=None, bias=None): |
218 # type: (Union[bytes, hgdate], Optional[Iterable[bytes]], Optional[Dict[bytes, bytes]]) -> hgdate | |
201 """parse a localized date/time and return a (unixtime, offset) tuple. | 219 """parse a localized date/time and return a (unixtime, offset) tuple. |
202 | 220 |
203 The date may be a "unixtime offset" string or in one of the specified | 221 The date may be a "unixtime offset" string or in one of the specified |
204 formats. If the date already is a (unixtime, offset) tuple, it is returned. | 222 formats. If the date already is a (unixtime, offset) tuple, it is returned. |
205 | 223 |
221 """ | 239 """ |
222 if bias is None: | 240 if bias is None: |
223 bias = {} | 241 bias = {} |
224 if not date: | 242 if not date: |
225 return 0, 0 | 243 return 0, 0 |
226 if isinstance(date, tuple) and len(date) == 2: | 244 if isinstance(date, tuple): |
227 return date | 245 if len(date) == 2: |
246 return date | |
247 else: | |
248 raise error.ProgrammingError(b"invalid date format") | |
228 if not formats: | 249 if not formats: |
229 formats = defaultdateformats | 250 formats = defaultdateformats |
230 date = date.strip() | 251 date = date.strip() |
231 | 252 |
232 if date == b'now' or date == _(b'now'): | 253 if date == b'now' or date == _(b'now'): |
282 raise error.ParseError(_(b'impossible time zone offset: %d') % offset) | 303 raise error.ParseError(_(b'impossible time zone offset: %d') % offset) |
283 return when, offset | 304 return when, offset |
284 | 305 |
285 | 306 |
286 def matchdate(date): | 307 def matchdate(date): |
308 # type: (bytes) -> Callable[[float], bool] | |
287 """Return a function that matches a given date match specifier | 309 """Return a function that matches a given date match specifier |
288 | 310 |
289 Formats include: | 311 Formats include: |
290 | 312 |
291 '{date}' match a given date to the accuracy provided | 313 '{date}' match a given date to the accuracy provided |
311 >>> f(p5[0]) | 333 >>> f(p5[0]) |
312 False | 334 False |
313 """ | 335 """ |
314 | 336 |
315 def lower(date): | 337 def lower(date): |
338 # type: (bytes) -> float | |
316 d = {b'mb': b"1", b'd': b"1"} | 339 d = {b'mb': b"1", b'd': b"1"} |
317 return parsedate(date, extendeddateformats, d)[0] | 340 return parsedate(date, extendeddateformats, d)[0] |
318 | 341 |
319 def upper(date): | 342 def upper(date): |
343 # type: (bytes) -> float | |
320 d = {b'mb': b"12", b'HI': b"23", b'M': b"59", b'S': b"59"} | 344 d = {b'mb': b"12", b'HI': b"23", b'M': b"59", b'S': b"59"} |
321 for days in (b"31", b"30", b"29"): | 345 for days in (b"31", b"30", b"29"): |
322 try: | 346 try: |
323 d[b"d"] = days | 347 d[b"d"] = days |
324 return parsedate(date, extendeddateformats, d)[0] | 348 return parsedate(date, extendeddateformats, d)[0] |