##// END OF EJS Templates
typing: add type annotations to mercurial/utils/dateutil.py...
Matt Harbison -
r47387:15c2f922 default
parent child Browse files
Show More
@@ -1,363 +1,387 b''
1 # util.py - Mercurial utility functions relative to dates
1 # util.py - Mercurial utility functions relative to dates
2 #
2 #
3 # Copyright 2018 Boris Feld <boris.feld@octobus.net>
3 # Copyright 2018 Boris Feld <boris.feld@octobus.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import calendar
10 import calendar
11 import datetime
11 import datetime
12 import time
12 import time
13
13
14 from ..i18n import _
14 from ..i18n import _
15 from .. import (
15 from .. import (
16 encoding,
16 encoding,
17 error,
17 error,
18 pycompat,
18 pycompat,
19 )
19 )
20
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)
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
25 b'%Y-%m-%dT%H%M%S', # another awful but legal variant without :
37 b'%Y-%m-%dT%H%M%S', # another awful but legal variant without :
26 b'%Y-%m-%dT%H%M', # without seconds
38 b'%Y-%m-%dT%H%M', # without seconds
27 b'%Y-%m-%d %H:%M:%S', # our common legal variant
39 b'%Y-%m-%d %H:%M:%S', # our common legal variant
28 b'%Y-%m-%d %H:%M', # without seconds
40 b'%Y-%m-%d %H:%M', # without seconds
29 b'%Y-%m-%d %H%M%S', # without :
41 b'%Y-%m-%d %H%M%S', # without :
30 b'%Y-%m-%d %H%M', # without seconds
42 b'%Y-%m-%d %H%M', # without seconds
31 b'%Y-%m-%d %I:%M:%S%p',
43 b'%Y-%m-%d %I:%M:%S%p',
32 b'%Y-%m-%d %H:%M',
44 b'%Y-%m-%d %H:%M',
33 b'%Y-%m-%d %I:%M%p',
45 b'%Y-%m-%d %I:%M%p',
34 b'%Y-%m-%d',
46 b'%Y-%m-%d',
35 b'%m-%d',
47 b'%m-%d',
36 b'%m/%d',
48 b'%m/%d',
37 b'%m/%d/%y',
49 b'%m/%d/%y',
38 b'%m/%d/%Y',
50 b'%m/%d/%Y',
39 b'%a %b %d %H:%M:%S %Y',
51 b'%a %b %d %H:%M:%S %Y',
40 b'%a %b %d %I:%M:%S%p %Y',
52 b'%a %b %d %I:%M:%S%p %Y',
41 b'%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
53 b'%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
42 b'%b %d %H:%M:%S %Y',
54 b'%b %d %H:%M:%S %Y',
43 b'%b %d %I:%M:%S%p %Y',
55 b'%b %d %I:%M:%S%p %Y',
44 b'%b %d %H:%M:%S',
56 b'%b %d %H:%M:%S',
45 b'%b %d %I:%M:%S%p',
57 b'%b %d %I:%M:%S%p',
46 b'%b %d %H:%M',
58 b'%b %d %H:%M',
47 b'%b %d %I:%M%p',
59 b'%b %d %I:%M%p',
48 b'%b %d %Y',
60 b'%b %d %Y',
49 b'%b %d',
61 b'%b %d',
50 b'%H:%M:%S',
62 b'%H:%M:%S',
51 b'%I:%M:%S%p',
63 b'%I:%M:%S%p',
52 b'%H:%M',
64 b'%H:%M',
53 b'%I:%M%p',
65 b'%I:%M%p',
54 )
66 )
55
67
56 extendeddateformats = defaultdateformats + (
68 extendeddateformats = defaultdateformats + (
57 b"%Y",
69 b"%Y",
58 b"%Y-%m",
70 b"%Y-%m",
59 b"%b",
71 b"%b",
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:
70 hint = _(b"check your clock")
83 hint = _(b"check your clock")
71 raise error.InputError(
84 raise error.InputError(
72 _(b"negative timestamp: %d") % timestamp, hint=hint
85 _(b"negative timestamp: %d") % timestamp, hint=hint
73 )
86 )
74 delta = datetime.datetime.utcfromtimestamp(
87 delta = datetime.datetime.utcfromtimestamp(
75 timestamp
88 timestamp
76 ) - datetime.datetime.fromtimestamp(timestamp)
89 ) - datetime.datetime.fromtimestamp(timestamp)
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))
87 'Thu Jan 01 00:00:00 1970 +0000'
101 'Thu Jan 01 00:00:00 1970 +0000'
88 >>> datestr((42, 0))
102 >>> datestr((42, 0))
89 'Thu Jan 01 00:00:42 1970 +0000'
103 'Thu Jan 01 00:00:42 1970 +0000'
90 >>> datestr((-42, 0))
104 >>> datestr((-42, 0))
91 'Wed Dec 31 23:59:18 1969 +0000'
105 'Wed Dec 31 23:59:18 1969 +0000'
92 >>> datestr((0x7fffffff, 0))
106 >>> datestr((0x7fffffff, 0))
93 'Tue Jan 19 03:14:07 2038 +0000'
107 'Tue Jan 19 03:14:07 2038 +0000'
94 >>> datestr((-0x80000000, 0))
108 >>> datestr((-0x80000000, 0))
95 'Fri Dec 13 20:45:52 1901 +0000'
109 'Fri Dec 13 20:45:52 1901 +0000'
96 """
110 """
97 t, tz = date or makedate()
111 t, tz = date or makedate()
98 if b"%1" in format or b"%2" in format or b"%z" in format:
112 if b"%1" in format or b"%2" in format or b"%z" in format:
99 sign = (tz > 0) and b"-" or b"+"
113 sign = (tz > 0) and b"-" or b"+"
100 minutes = abs(tz) // 60
114 minutes = abs(tz) // 60
101 q, r = divmod(minutes, 60)
115 q, r = divmod(minutes, 60)
102 format = format.replace(b"%z", b"%1%2")
116 format = format.replace(b"%z", b"%1%2")
103 format = format.replace(b"%1", b"%c%02d" % (sign, q))
117 format = format.replace(b"%1", b"%c%02d" % (sign, q))
104 format = format.replace(b"%2", b"%02d" % r)
118 format = format.replace(b"%2", b"%02d" % r)
105 d = t - tz
119 d = t - tz
106 if d > 0x7FFFFFFF:
120 if d > 0x7FFFFFFF:
107 d = 0x7FFFFFFF
121 d = 0x7FFFFFFF
108 elif d < -0x80000000:
122 elif d < -0x80000000:
109 d = -0x80000000
123 d = -0x80000000
110 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
124 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
111 # because they use the gmtime() system call which is buggy on Windows
125 # because they use the gmtime() system call which is buggy on Windows
112 # for negative values.
126 # for negative values.
113 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
127 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
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"):
129 return 0, s[:-3].rstrip()
145 return 0, s[:-3].rstrip()
130
146
131 # Unix-style timezones [+-]hhmm
147 # Unix-style timezones [+-]hhmm
132 if len(s) >= 5 and s[-5] in b"+-" and s[-4:].isdigit():
148 if len(s) >= 5 and s[-5] in b"+-" and s[-4:].isdigit():
133 sign = (s[-5] == b"+") and 1 or -1
149 sign = (s[-5] == b"+") and 1 or -1
134 hours = int(s[-4:-2])
150 hours = int(s[-4:-2])
135 minutes = int(s[-2:])
151 minutes = int(s[-2:])
136 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
152 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
137
153
138 # ISO8601 trailing Z
154 # ISO8601 trailing Z
139 if s.endswith(b"Z") and s[-2:-1].isdigit():
155 if s.endswith(b"Z") and s[-2:-1].isdigit():
140 return 0, s[:-1]
156 return 0, s[:-1]
141
157
142 # ISO8601-style [+-]hh:mm
158 # ISO8601-style [+-]hh:mm
143 if (
159 if (
144 len(s) >= 6
160 len(s) >= 6
145 and s[-6] in b"+-"
161 and s[-6] in b"+-"
146 and s[-3] == b":"
162 and s[-3] == b":"
147 and s[-5:-3].isdigit()
163 and s[-5:-3].isdigit()
148 and s[-2:].isdigit()
164 and s[-2:].isdigit()
149 ):
165 ):
150 sign = (s[-6] == b"+") and 1 or -1
166 sign = (s[-6] == b"+") and 1 or -1
151 hours = int(s[-5:-3])
167 hours = int(s[-5:-3])
152 minutes = int(s[-2:])
168 minutes = int(s[-2:])
153 return -sign * (hours * 60 + minutes) * 60, s[:-6]
169 return -sign * (hours * 60 + minutes) * 60, s[:-6]
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
164 # NOTE: unixtime = localunixtime + offset
181 # NOTE: unixtime = localunixtime + offset
165 offset, date = parsetimezone(string)
182 offset, date = parsetimezone(string)
166
183
167 # add missing elements from defaults
184 # add missing elements from defaults
168 usenow = False # default to using biased defaults
185 usenow = False # default to using biased defaults
169 for part in (
186 for part in (
170 b"S",
187 b"S",
171 b"M",
188 b"M",
172 b"HI",
189 b"HI",
173 b"d",
190 b"d",
174 b"mb",
191 b"mb",
175 b"yY",
192 b"yY",
176 ): # decreasing specificity
193 ): # decreasing specificity
177 part = pycompat.bytestr(part)
194 part = pycompat.bytestr(part)
178 found = [True for p in part if (b"%" + p) in format]
195 found = [True for p in part if (b"%" + p) in format]
179 if not found:
196 if not found:
180 date += b"@" + defaults[part][usenow]
197 date += b"@" + defaults[part][usenow]
181 format += b"@%" + part[0]
198 format += b"@%" + part[0]
182 else:
199 else:
183 # We've found a specific time element, less specific time
200 # We've found a specific time element, less specific time
184 # elements are relative to today
201 # elements are relative to today
185 usenow = True
202 usenow = True
186
203
187 timetuple = time.strptime(
204 timetuple = time.strptime(
188 encoding.strfromlocal(date), encoding.strfromlocal(format)
205 encoding.strfromlocal(date), encoding.strfromlocal(format)
189 )
206 )
190 localunixtime = int(calendar.timegm(timetuple))
207 localunixtime = int(calendar.timegm(timetuple))
191 if offset is None:
208 if offset is None:
192 # local timezone
209 # local timezone
193 unixtime = int(time.mktime(timetuple))
210 unixtime = int(time.mktime(timetuple))
194 offset = unixtime - localunixtime
211 offset = unixtime - localunixtime
195 else:
212 else:
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
206 >>> parsedate(b' today ') == parsedate(
224 >>> parsedate(b' today ') == parsedate(
207 ... datetime.date.today().strftime('%b %d').encode('ascii'))
225 ... datetime.date.today().strftime('%b %d').encode('ascii'))
208 True
226 True
209 >>> parsedate(b'yesterday ') == parsedate(
227 >>> parsedate(b'yesterday ') == parsedate(
210 ... (datetime.date.today() - datetime.timedelta(days=1)
228 ... (datetime.date.today() - datetime.timedelta(days=1)
211 ... ).strftime('%b %d').encode('ascii'))
229 ... ).strftime('%b %d').encode('ascii'))
212 True
230 True
213 >>> now, tz = makedate()
231 >>> now, tz = makedate()
214 >>> strnow, strtz = parsedate(b'now')
232 >>> strnow, strtz = parsedate(b'now')
215 >>> (strnow - now) < 1
233 >>> (strnow - now) < 1
216 True
234 True
217 >>> tz == strtz
235 >>> tz == strtz
218 True
236 True
219 >>> parsedate(b'2000 UTC', formats=extendeddateformats)
237 >>> parsedate(b'2000 UTC', formats=extendeddateformats)
220 (946684800, 0)
238 (946684800, 0)
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'):
233 return makedate()
254 return makedate()
234 if date == b'today' or date == _(b'today'):
255 if date == b'today' or date == _(b'today'):
235 date = datetime.date.today().strftime('%b %d')
256 date = datetime.date.today().strftime('%b %d')
236 date = encoding.strtolocal(date)
257 date = encoding.strtolocal(date)
237 elif date == b'yesterday' or date == _(b'yesterday'):
258 elif date == b'yesterday' or date == _(b'yesterday'):
238 date = (datetime.date.today() - datetime.timedelta(days=1)).strftime(
259 date = (datetime.date.today() - datetime.timedelta(days=1)).strftime(
239 r'%b %d'
260 r'%b %d'
240 )
261 )
241 date = encoding.strtolocal(date)
262 date = encoding.strtolocal(date)
242
263
243 try:
264 try:
244 when, offset = map(int, date.split(b' '))
265 when, offset = map(int, date.split(b' '))
245 except ValueError:
266 except ValueError:
246 # fill out defaults
267 # fill out defaults
247 now = makedate()
268 now = makedate()
248 defaults = {}
269 defaults = {}
249 for part in (b"d", b"mb", b"yY", b"HI", b"M", b"S"):
270 for part in (b"d", b"mb", b"yY", b"HI", b"M", b"S"):
250 # this piece is for rounding the specific end of unknowns
271 # this piece is for rounding the specific end of unknowns
251 b = bias.get(part)
272 b = bias.get(part)
252 if b is None:
273 if b is None:
253 if part[0:1] in b"HMS":
274 if part[0:1] in b"HMS":
254 b = b"00"
275 b = b"00"
255 else:
276 else:
256 # year, month, and day start from 1
277 # year, month, and day start from 1
257 b = b"1"
278 b = b"1"
258
279
259 # this piece is for matching the generic end to today's date
280 # this piece is for matching the generic end to today's date
260 n = datestr(now, b"%" + part[0:1])
281 n = datestr(now, b"%" + part[0:1])
261
282
262 defaults[part] = (b, n)
283 defaults[part] = (b, n)
263
284
264 for format in formats:
285 for format in formats:
265 try:
286 try:
266 when, offset = strdate(date, format, defaults)
287 when, offset = strdate(date, format, defaults)
267 except (ValueError, OverflowError):
288 except (ValueError, OverflowError):
268 pass
289 pass
269 else:
290 else:
270 break
291 break
271 else:
292 else:
272 raise error.ParseError(
293 raise error.ParseError(
273 _(b'invalid date: %r') % pycompat.bytestr(date)
294 _(b'invalid date: %r') % pycompat.bytestr(date)
274 )
295 )
275 # validate explicit (probably user-specified) date and
296 # validate explicit (probably user-specified) date and
276 # time zone offset. values must fit in signed 32 bits for
297 # time zone offset. values must fit in signed 32 bits for
277 # current 32-bit linux runtimes. timezones go from UTC-12
298 # current 32-bit linux runtimes. timezones go from UTC-12
278 # to UTC+14
299 # to UTC+14
279 if when < -0x80000000 or when > 0x7FFFFFFF:
300 if when < -0x80000000 or when > 0x7FFFFFFF:
280 raise error.ParseError(_(b'date exceeds 32 bits: %d') % when)
301 raise error.ParseError(_(b'date exceeds 32 bits: %d') % when)
281 if offset < -50400 or offset > 43200:
302 if offset < -50400 or offset > 43200:
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
292
314
293 '<{date}' on or before a given date
315 '<{date}' on or before a given date
294
316
295 '>{date}' on or after a given date
317 '>{date}' on or after a given date
296
318
297 >>> p1 = parsedate(b"10:29:59")
319 >>> p1 = parsedate(b"10:29:59")
298 >>> p2 = parsedate(b"10:30:00")
320 >>> p2 = parsedate(b"10:30:00")
299 >>> p3 = parsedate(b"10:30:59")
321 >>> p3 = parsedate(b"10:30:59")
300 >>> p4 = parsedate(b"10:31:00")
322 >>> p4 = parsedate(b"10:31:00")
301 >>> p5 = parsedate(b"Sep 15 10:30:00 1999")
323 >>> p5 = parsedate(b"Sep 15 10:30:00 1999")
302 >>> f = matchdate(b"10:30")
324 >>> f = matchdate(b"10:30")
303 >>> f(p1[0])
325 >>> f(p1[0])
304 False
326 False
305 >>> f(p2[0])
327 >>> f(p2[0])
306 True
328 True
307 >>> f(p3[0])
329 >>> f(p3[0])
308 True
330 True
309 >>> f(p4[0])
331 >>> f(p4[0])
310 False
332 False
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]
325 except error.ParseError:
349 except error.ParseError:
326 pass
350 pass
327 d[b"d"] = b"28"
351 d[b"d"] = b"28"
328 return parsedate(date, extendeddateformats, d)[0]
352 return parsedate(date, extendeddateformats, d)[0]
329
353
330 date = date.strip()
354 date = date.strip()
331
355
332 if not date:
356 if not date:
333 raise error.InputError(
357 raise error.InputError(
334 _(b"dates cannot consist entirely of whitespace")
358 _(b"dates cannot consist entirely of whitespace")
335 )
359 )
336 elif date[0:1] == b"<":
360 elif date[0:1] == b"<":
337 if not date[1:]:
361 if not date[1:]:
338 raise error.InputError(_(b"invalid day spec, use '<DATE'"))
362 raise error.InputError(_(b"invalid day spec, use '<DATE'"))
339 when = upper(date[1:])
363 when = upper(date[1:])
340 return lambda x: x <= when
364 return lambda x: x <= when
341 elif date[0:1] == b">":
365 elif date[0:1] == b">":
342 if not date[1:]:
366 if not date[1:]:
343 raise error.InputError(_(b"invalid day spec, use '>DATE'"))
367 raise error.InputError(_(b"invalid day spec, use '>DATE'"))
344 when = lower(date[1:])
368 when = lower(date[1:])
345 return lambda x: x >= when
369 return lambda x: x >= when
346 elif date[0:1] == b"-":
370 elif date[0:1] == b"-":
347 try:
371 try:
348 days = int(date[1:])
372 days = int(date[1:])
349 except ValueError:
373 except ValueError:
350 raise error.InputError(_(b"invalid day spec: %s") % date[1:])
374 raise error.InputError(_(b"invalid day spec: %s") % date[1:])
351 if days < 0:
375 if days < 0:
352 raise error.InputError(
376 raise error.InputError(
353 _(b"%s must be nonnegative (see 'hg help dates')") % date[1:]
377 _(b"%s must be nonnegative (see 'hg help dates')") % date[1:]
354 )
378 )
355 when = makedate()[0] - days * 3600 * 24
379 when = makedate()[0] - days * 3600 * 24
356 return lambda x: x >= when
380 return lambda x: x >= when
357 elif b" to " in date:
381 elif b" to " in date:
358 a, b = date.split(b" to ")
382 a, b = date.split(b" to ")
359 start, stop = lower(a), upper(b)
383 start, stop = lower(a), upper(b)
360 return lambda x: x >= start and x <= stop
384 return lambda x: x >= start and x <= stop
361 else:
385 else:
362 start, stop = lower(date), upper(date)
386 start, stop = lower(date), upper(date)
363 return lambda x: x >= start and x <= stop
387 return lambda x: x >= start and x <= stop
General Comments 0
You need to be logged in to leave comments. Login now