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]