##// END OF EJS Templates
utils: test coverage of makedate...
Mads Kiilerich -
r52997:0b05b4d8 stable
parent child Browse files
Show More
@@ -1,401 +1,431
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
8
9 import calendar
9 import calendar
10 import datetime
10 import datetime
11 import time
11 import time
12
12
13 from typing import (
13 from typing import (
14 Callable,
14 Callable,
15 Dict,
15 Dict,
16 Iterable,
16 Iterable,
17 Optional,
17 Optional,
18 Tuple,
18 Tuple,
19 Union,
19 Union,
20 )
20 )
21
21
22 from ..i18n import _
22 from ..i18n import _
23 from .. import (
23 from .. import (
24 encoding,
24 encoding,
25 error,
25 error,
26 pycompat,
26 pycompat,
27 )
27 )
28
28
29 # keeps pyflakes happy
29 # keeps pyflakes happy
30 assert [
30 assert [
31 Callable,
31 Callable,
32 Dict,
32 Dict,
33 Iterable,
33 Iterable,
34 Optional,
34 Optional,
35 Tuple,
35 Tuple,
36 Union,
36 Union,
37 ]
37 ]
38
38
39 hgdate = Tuple[float, int] # (unixtime, offset)
39 hgdate = Tuple[float, int] # (unixtime, offset)
40
40
41 # used by parsedate
41 # used by parsedate
42 defaultdateformats = (
42 defaultdateformats = (
43 b'%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
43 b'%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
44 b'%Y-%m-%dT%H:%M', # without seconds
44 b'%Y-%m-%dT%H:%M', # without seconds
45 b'%Y-%m-%dT%H%M%S', # another awful but legal variant without :
45 b'%Y-%m-%dT%H%M%S', # another awful but legal variant without :
46 b'%Y-%m-%dT%H%M', # without seconds
46 b'%Y-%m-%dT%H%M', # without seconds
47 b'%Y-%m-%d %H:%M:%S', # our common legal variant
47 b'%Y-%m-%d %H:%M:%S', # our common legal variant
48 b'%Y-%m-%d %H:%M', # without seconds
48 b'%Y-%m-%d %H:%M', # without seconds
49 b'%Y-%m-%d %H%M%S', # without :
49 b'%Y-%m-%d %H%M%S', # without :
50 b'%Y-%m-%d %H%M', # without seconds
50 b'%Y-%m-%d %H%M', # without seconds
51 b'%Y-%m-%d %I:%M:%S%p',
51 b'%Y-%m-%d %I:%M:%S%p',
52 b'%Y-%m-%d %H:%M',
52 b'%Y-%m-%d %H:%M',
53 b'%Y-%m-%d %I:%M%p',
53 b'%Y-%m-%d %I:%M%p',
54 b'%Y-%m-%d',
54 b'%Y-%m-%d',
55 b'%m-%d',
55 b'%m-%d',
56 b'%m/%d',
56 b'%m/%d',
57 b'%m/%d/%y',
57 b'%m/%d/%y',
58 b'%m/%d/%Y',
58 b'%m/%d/%Y',
59 b'%a %b %d %H:%M:%S %Y',
59 b'%a %b %d %H:%M:%S %Y',
60 b'%a %b %d %I:%M:%S%p %Y',
60 b'%a %b %d %I:%M:%S%p %Y',
61 b'%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
61 b'%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
62 b'%b %d %H:%M:%S %Y',
62 b'%b %d %H:%M:%S %Y',
63 b'%b %d %I:%M:%S%p %Y',
63 b'%b %d %I:%M:%S%p %Y',
64 b'%b %d %H:%M:%S',
64 b'%b %d %H:%M:%S',
65 b'%b %d %I:%M:%S%p',
65 b'%b %d %I:%M:%S%p',
66 b'%b %d %H:%M',
66 b'%b %d %H:%M',
67 b'%b %d %I:%M%p',
67 b'%b %d %I:%M%p',
68 b'%b %d %Y',
68 b'%b %d %Y',
69 b'%b %d',
69 b'%b %d',
70 b'%H:%M:%S',
70 b'%H:%M:%S',
71 b'%I:%M:%S%p',
71 b'%I:%M:%S%p',
72 b'%H:%M',
72 b'%H:%M',
73 b'%I:%M%p',
73 b'%I:%M%p',
74 )
74 )
75
75
76 extendeddateformats = defaultdateformats + (
76 extendeddateformats = defaultdateformats + (
77 b"%Y",
77 b"%Y",
78 b"%Y-%m",
78 b"%Y-%m",
79 b"%b",
79 b"%b",
80 b"%b %Y",
80 b"%b %Y",
81 )
81 )
82
82
83
83
84 def makedate(timestamp: Optional[float] = None) -> hgdate:
84 def makedate(timestamp: Optional[float] = None) -> hgdate:
85 """Return a unix timestamp (or the current time) as a (unixtime,
85 """Return a unix timestamp (or the current time) as a (unixtime,
86 offset) tuple based off the local timezone."""
86 offset) tuple based off the local timezone.
87
88 >>> import os, time
89 >>> os.environ['TZ'] = 'Asia/Novokuznetsk'
90 >>> time.tzset()
91
92 >>> def dtu(*a):
93 ... return datetime.datetime(*a, tzinfo=datetime.timezone.utc)
94
95 # Old winter timezone, +7
96 >>> makedate(dtu(2010, 1, 1, 5, 0, 0).timestamp())
97 (1262322000.0, -25200)
98
99 # Same timezone in summer, +7, so no DST
100 >>> makedate(dtu(2010, 7, 1, 5, 0, 0).timestamp())
101 (1277960400.0, -25200)
102
103 # Changing to new winter timezone, from +7 to +6 (ae04af1ce78d testcase)
104 >>> makedate(dtu(2010, 10, 30, 20, 0, 0).timestamp() - 1)
105 (1288468799.0, -25200)
106 >>> makedate(dtu(2010, 10, 30, 20, 0, 0).timestamp())
107 (1288468800.0, -21600)
108 >>> makedate(dtu(2011, 1, 1, 5, 0, 0).timestamp())
109 (1293858000.0, -21600)
110
111 # Introducing DST, changing +6 to +7
112 >>> makedate(dtu(2011, 3, 26, 20, 0, 0).timestamp() - 1)
113 (1301169599.0, -21600)
114 >>> makedate(dtu(2011, 3, 26, 20, 0, 0).timestamp())
115 (1301169600.0, -25200)
116 """
87 if timestamp is None:
117 if timestamp is None:
88 timestamp = time.time()
118 timestamp = time.time()
89 if timestamp < 0:
119 if timestamp < 0:
90 hint = _(b"check your clock")
120 hint = _(b"check your clock")
91 raise error.InputError(
121 raise error.InputError(
92 _(b"negative timestamp: %d") % timestamp, hint=hint
122 _(b"negative timestamp: %d") % timestamp, hint=hint
93 )
123 )
94 tz = round(
124 tz = round(
95 timestamp
125 timestamp
96 - datetime.datetime.fromtimestamp(
126 - datetime.datetime.fromtimestamp(
97 timestamp,
127 timestamp,
98 )
128 )
99 .replace(tzinfo=datetime.timezone.utc)
129 .replace(tzinfo=datetime.timezone.utc)
100 .timestamp()
130 .timestamp()
101 )
131 )
102 return timestamp, tz
132 return timestamp, tz
103
133
104
134
105 def datestr(
135 def datestr(
106 date: Optional[hgdate] = None,
136 date: Optional[hgdate] = None,
107 format: bytes = b'%a %b %d %H:%M:%S %Y %1%2',
137 format: bytes = b'%a %b %d %H:%M:%S %Y %1%2',
108 ) -> bytes:
138 ) -> bytes:
109 """represent a (unixtime, offset) tuple as a localized time.
139 """represent a (unixtime, offset) tuple as a localized time.
110 unixtime is seconds since the epoch, and offset is the time zone's
140 unixtime is seconds since the epoch, and offset is the time zone's
111 number of seconds away from UTC.
141 number of seconds away from UTC.
112
142
113 >>> datestr((0, 0))
143 >>> datestr((0, 0))
114 'Thu Jan 01 00:00:00 1970 +0000'
144 'Thu Jan 01 00:00:00 1970 +0000'
115 >>> datestr((42, 0))
145 >>> datestr((42, 0))
116 'Thu Jan 01 00:00:42 1970 +0000'
146 'Thu Jan 01 00:00:42 1970 +0000'
117 >>> datestr((-42, 0))
147 >>> datestr((-42, 0))
118 'Wed Dec 31 23:59:18 1969 +0000'
148 'Wed Dec 31 23:59:18 1969 +0000'
119 >>> datestr((0x7fffffff, 0))
149 >>> datestr((0x7fffffff, 0))
120 'Tue Jan 19 03:14:07 2038 +0000'
150 'Tue Jan 19 03:14:07 2038 +0000'
121 >>> datestr((-0x80000000, 0))
151 >>> datestr((-0x80000000, 0))
122 'Fri Dec 13 20:45:52 1901 +0000'
152 'Fri Dec 13 20:45:52 1901 +0000'
123 """
153 """
124 t, tz = date or makedate()
154 t, tz = date or makedate()
125 if b"%1" in format or b"%2" in format or b"%z" in format:
155 if b"%1" in format or b"%2" in format or b"%z" in format:
126 sign = (tz > 0) and b"-" or b"+"
156 sign = (tz > 0) and b"-" or b"+"
127 minutes = abs(tz) // 60
157 minutes = abs(tz) // 60
128 q, r = divmod(minutes, 60)
158 q, r = divmod(minutes, 60)
129 format = format.replace(b"%z", b"%1%2")
159 format = format.replace(b"%z", b"%1%2")
130 format = format.replace(b"%1", b"%c%02d" % (sign, q))
160 format = format.replace(b"%1", b"%c%02d" % (sign, q))
131 format = format.replace(b"%2", b"%02d" % r)
161 format = format.replace(b"%2", b"%02d" % r)
132 d = t - tz
162 d = t - tz
133 if d > 0x7FFFFFFF:
163 if d > 0x7FFFFFFF:
134 d = 0x7FFFFFFF
164 d = 0x7FFFFFFF
135 elif d < -0x80000000:
165 elif d < -0x80000000:
136 d = -0x80000000
166 d = -0x80000000
137 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
167 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
138 # because they use the gmtime() system call which is buggy on Windows
168 # because they use the gmtime() system call which is buggy on Windows
139 # for negative values.
169 # for negative values.
140 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
170 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
141 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
171 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
142 return s
172 return s
143
173
144
174
145 def shortdate(date: Optional[hgdate] = None) -> bytes:
175 def shortdate(date: Optional[hgdate] = None) -> bytes:
146 """turn (timestamp, tzoff) tuple into iso 8631 date."""
176 """turn (timestamp, tzoff) tuple into iso 8631 date."""
147 return datestr(date, format=b'%Y-%m-%d')
177 return datestr(date, format=b'%Y-%m-%d')
148
178
149
179
150 def parsetimezone(s: bytes) -> Tuple[Optional[int], bytes]:
180 def parsetimezone(s: bytes) -> Tuple[Optional[int], bytes]:
151 """find a trailing timezone, if any, in string, and return a
181 """find a trailing timezone, if any, in string, and return a
152 (offset, remainder) pair"""
182 (offset, remainder) pair"""
153 s = pycompat.bytestr(s)
183 s = pycompat.bytestr(s)
154
184
155 if s.endswith(b"GMT") or s.endswith(b"UTC"):
185 if s.endswith(b"GMT") or s.endswith(b"UTC"):
156 return 0, s[:-3].rstrip()
186 return 0, s[:-3].rstrip()
157
187
158 # Unix-style timezones [+-]hhmm
188 # Unix-style timezones [+-]hhmm
159 if len(s) >= 5 and s[-5] in b"+-" and s[-4:].isdigit():
189 if len(s) >= 5 and s[-5] in b"+-" and s[-4:].isdigit():
160 sign = (s[-5] == b"+") and 1 or -1
190 sign = (s[-5] == b"+") and 1 or -1
161 hours = int(s[-4:-2])
191 hours = int(s[-4:-2])
162 minutes = int(s[-2:])
192 minutes = int(s[-2:])
163 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
193 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
164
194
165 # ISO8601 trailing Z
195 # ISO8601 trailing Z
166 if s.endswith(b"Z") and s[-2:-1].isdigit():
196 if s.endswith(b"Z") and s[-2:-1].isdigit():
167 return 0, s[:-1]
197 return 0, s[:-1]
168
198
169 # ISO8601-style [+-]hh:mm
199 # ISO8601-style [+-]hh:mm
170 if (
200 if (
171 len(s) >= 6
201 len(s) >= 6
172 and s[-6] in b"+-"
202 and s[-6] in b"+-"
173 and s[-3] == b":"
203 and s[-3] == b":"
174 and s[-5:-3].isdigit()
204 and s[-5:-3].isdigit()
175 and s[-2:].isdigit()
205 and s[-2:].isdigit()
176 ):
206 ):
177 sign = (s[-6] == b"+") and 1 or -1
207 sign = (s[-6] == b"+") and 1 or -1
178 hours = int(s[-5:-3])
208 hours = int(s[-5:-3])
179 minutes = int(s[-2:])
209 minutes = int(s[-2:])
180 return -sign * (hours * 60 + minutes) * 60, s[:-6]
210 return -sign * (hours * 60 + minutes) * 60, s[:-6]
181
211
182 return None, s
212 return None, s
183
213
184
214
185 def strdate(
215 def strdate(
186 string: bytes,
216 string: bytes,
187 format: bytes,
217 format: bytes,
188 defaults: Optional[Dict[bytes, Tuple[bytes, bytes]]] = None,
218 defaults: Optional[Dict[bytes, Tuple[bytes, bytes]]] = None,
189 ) -> hgdate:
219 ) -> hgdate:
190 """parse a localized time string and return a (unixtime, offset) tuple.
220 """parse a localized time string and return a (unixtime, offset) tuple.
191 if the string cannot be parsed, ValueError is raised."""
221 if the string cannot be parsed, ValueError is raised."""
192 if defaults is None:
222 if defaults is None:
193 defaults = {}
223 defaults = {}
194
224
195 # NOTE: unixtime = localunixtime + offset
225 # NOTE: unixtime = localunixtime + offset
196 offset, date = parsetimezone(string)
226 offset, date = parsetimezone(string)
197
227
198 # add missing elements from defaults
228 # add missing elements from defaults
199 usenow = False # default to using biased defaults
229 usenow = False # default to using biased defaults
200 for part in (
230 for part in (
201 b"S",
231 b"S",
202 b"M",
232 b"M",
203 b"HI",
233 b"HI",
204 b"d",
234 b"d",
205 b"mb",
235 b"mb",
206 b"yY",
236 b"yY",
207 ): # decreasing specificity
237 ): # decreasing specificity
208 part = pycompat.bytestr(part)
238 part = pycompat.bytestr(part)
209 found = [True for p in part if (b"%" + p) in format]
239 found = [True for p in part if (b"%" + p) in format]
210 if not found:
240 if not found:
211 date += b"@" + defaults[part][usenow]
241 date += b"@" + defaults[part][usenow]
212 format += b"@%" + part[0]
242 format += b"@%" + part[0]
213 else:
243 else:
214 # We've found a specific time element, less specific time
244 # We've found a specific time element, less specific time
215 # elements are relative to today
245 # elements are relative to today
216 usenow = True
246 usenow = True
217
247
218 timetuple = time.strptime(
248 timetuple = time.strptime(
219 encoding.strfromlocal(date), encoding.strfromlocal(format)
249 encoding.strfromlocal(date), encoding.strfromlocal(format)
220 )
250 )
221 localunixtime = int(calendar.timegm(timetuple))
251 localunixtime = int(calendar.timegm(timetuple))
222 if offset is None:
252 if offset is None:
223 # local timezone
253 # local timezone
224 unixtime = int(time.mktime(timetuple))
254 unixtime = int(time.mktime(timetuple))
225 offset = unixtime - localunixtime
255 offset = unixtime - localunixtime
226 else:
256 else:
227 unixtime = localunixtime + offset
257 unixtime = localunixtime + offset
228 return unixtime, offset
258 return unixtime, offset
229
259
230
260
231 def parsedate(
261 def parsedate(
232 date: Union[bytes, hgdate],
262 date: Union[bytes, hgdate],
233 formats: Optional[Iterable[bytes]] = None,
263 formats: Optional[Iterable[bytes]] = None,
234 bias: Optional[Dict[bytes, bytes]] = None,
264 bias: Optional[Dict[bytes, bytes]] = None,
235 ) -> hgdate:
265 ) -> hgdate:
236 """parse a localized date/time and return a (unixtime, offset) tuple.
266 """parse a localized date/time and return a (unixtime, offset) tuple.
237
267
238 The date may be a "unixtime offset" string or in one of the specified
268 The date may be a "unixtime offset" string or in one of the specified
239 formats. If the date already is a (unixtime, offset) tuple, it is returned.
269 formats. If the date already is a (unixtime, offset) tuple, it is returned.
240
270
241 >>> parsedate(b' today ') == parsedate(
271 >>> parsedate(b' today ') == parsedate(
242 ... datetime.date.today().strftime('%b %d').encode('ascii'))
272 ... datetime.date.today().strftime('%b %d').encode('ascii'))
243 True
273 True
244 >>> parsedate(b'yesterday ') == parsedate(
274 >>> parsedate(b'yesterday ') == parsedate(
245 ... (datetime.date.today() - datetime.timedelta(days=1)
275 ... (datetime.date.today() - datetime.timedelta(days=1)
246 ... ).strftime('%b %d').encode('ascii'))
276 ... ).strftime('%b %d').encode('ascii'))
247 True
277 True
248 >>> now, tz = makedate()
278 >>> now, tz = makedate()
249 >>> strnow, strtz = parsedate(b'now')
279 >>> strnow, strtz = parsedate(b'now')
250 >>> (strnow - now) < 1
280 >>> (strnow - now) < 1
251 True
281 True
252 >>> tz == strtz
282 >>> tz == strtz
253 True
283 True
254 >>> parsedate(b'2000 UTC', formats=extendeddateformats)
284 >>> parsedate(b'2000 UTC', formats=extendeddateformats)
255 (946684800, 0)
285 (946684800, 0)
256 """
286 """
257 if bias is None:
287 if bias is None:
258 bias = {}
288 bias = {}
259 if not date:
289 if not date:
260 return 0, 0
290 return 0, 0
261 if isinstance(date, tuple):
291 if isinstance(date, tuple):
262 if len(date) == 2:
292 if len(date) == 2:
263 return date
293 return date
264 else:
294 else:
265 raise error.ProgrammingError(b"invalid date format")
295 raise error.ProgrammingError(b"invalid date format")
266 if not formats:
296 if not formats:
267 formats = defaultdateformats
297 formats = defaultdateformats
268 date = date.strip()
298 date = date.strip()
269
299
270 if date == b'now' or date == _(b'now'):
300 if date == b'now' or date == _(b'now'):
271 return makedate()
301 return makedate()
272 if date == b'today' or date == _(b'today'):
302 if date == b'today' or date == _(b'today'):
273 date = datetime.date.today().strftime('%b %d')
303 date = datetime.date.today().strftime('%b %d')
274 date = encoding.strtolocal(date)
304 date = encoding.strtolocal(date)
275 elif date == b'yesterday' or date == _(b'yesterday'):
305 elif date == b'yesterday' or date == _(b'yesterday'):
276 date = (datetime.date.today() - datetime.timedelta(days=1)).strftime(
306 date = (datetime.date.today() - datetime.timedelta(days=1)).strftime(
277 r'%b %d'
307 r'%b %d'
278 )
308 )
279 date = encoding.strtolocal(date)
309 date = encoding.strtolocal(date)
280
310
281 try:
311 try:
282 when, offset = map(int, date.split(b' '))
312 when, offset = map(int, date.split(b' '))
283 except ValueError:
313 except ValueError:
284 # fill out defaults
314 # fill out defaults
285 now = makedate()
315 now = makedate()
286 defaults = {}
316 defaults = {}
287 for part in (b"d", b"mb", b"yY", b"HI", b"M", b"S"):
317 for part in (b"d", b"mb", b"yY", b"HI", b"M", b"S"):
288 # this piece is for rounding the specific end of unknowns
318 # this piece is for rounding the specific end of unknowns
289 b = bias.get(part)
319 b = bias.get(part)
290 if b is None:
320 if b is None:
291 if part[0:1] in b"HMS":
321 if part[0:1] in b"HMS":
292 b = b"00"
322 b = b"00"
293 else:
323 else:
294 # year, month, and day start from 1
324 # year, month, and day start from 1
295 b = b"1"
325 b = b"1"
296
326
297 # this piece is for matching the generic end to today's date
327 # this piece is for matching the generic end to today's date
298 n = datestr(now, b"%" + part[0:1])
328 n = datestr(now, b"%" + part[0:1])
299
329
300 defaults[part] = (b, n)
330 defaults[part] = (b, n)
301
331
302 for format in formats:
332 for format in formats:
303 try:
333 try:
304 when, offset = strdate(date, format, defaults)
334 when, offset = strdate(date, format, defaults)
305 except (ValueError, OverflowError):
335 except (ValueError, OverflowError):
306 pass
336 pass
307 else:
337 else:
308 break
338 break
309 else:
339 else:
310 raise error.ParseError(
340 raise error.ParseError(
311 _(b'invalid date: %r') % pycompat.bytestr(date)
341 _(b'invalid date: %r') % pycompat.bytestr(date)
312 )
342 )
313 # validate explicit (probably user-specified) date and
343 # validate explicit (probably user-specified) date and
314 # time zone offset. values must fit in signed 32 bits for
344 # time zone offset. values must fit in signed 32 bits for
315 # current 32-bit linux runtimes. timezones go from UTC-12
345 # current 32-bit linux runtimes. timezones go from UTC-12
316 # to UTC+14
346 # to UTC+14
317 if when < -0x80000000 or when > 0x7FFFFFFF:
347 if when < -0x80000000 or when > 0x7FFFFFFF:
318 raise error.ParseError(_(b'date exceeds 32 bits: %d') % when)
348 raise error.ParseError(_(b'date exceeds 32 bits: %d') % when)
319 if offset < -50400 or offset > 43200:
349 if offset < -50400 or offset > 43200:
320 raise error.ParseError(_(b'impossible time zone offset: %d') % offset)
350 raise error.ParseError(_(b'impossible time zone offset: %d') % offset)
321 return when, offset
351 return when, offset
322
352
323
353
324 def matchdate(date: bytes) -> Callable[[float], bool]:
354 def matchdate(date: bytes) -> Callable[[float], bool]:
325 """Return a function that matches a given date match specifier
355 """Return a function that matches a given date match specifier
326
356
327 Formats include:
357 Formats include:
328
358
329 '{date}' match a given date to the accuracy provided
359 '{date}' match a given date to the accuracy provided
330
360
331 '<{date}' on or before a given date
361 '<{date}' on or before a given date
332
362
333 '>{date}' on or after a given date
363 '>{date}' on or after a given date
334
364
335 >>> p1 = parsedate(b"10:29:59")
365 >>> p1 = parsedate(b"10:29:59")
336 >>> p2 = parsedate(b"10:30:00")
366 >>> p2 = parsedate(b"10:30:00")
337 >>> p3 = parsedate(b"10:30:59")
367 >>> p3 = parsedate(b"10:30:59")
338 >>> p4 = parsedate(b"10:31:00")
368 >>> p4 = parsedate(b"10:31:00")
339 >>> p5 = parsedate(b"Sep 15 10:30:00 1999")
369 >>> p5 = parsedate(b"Sep 15 10:30:00 1999")
340 >>> f = matchdate(b"10:30")
370 >>> f = matchdate(b"10:30")
341 >>> f(p1[0])
371 >>> f(p1[0])
342 False
372 False
343 >>> f(p2[0])
373 >>> f(p2[0])
344 True
374 True
345 >>> f(p3[0])
375 >>> f(p3[0])
346 True
376 True
347 >>> f(p4[0])
377 >>> f(p4[0])
348 False
378 False
349 >>> f(p5[0])
379 >>> f(p5[0])
350 False
380 False
351 """
381 """
352
382
353 def lower(date: bytes) -> float:
383 def lower(date: bytes) -> float:
354 d = {b'mb': b"1", b'd': b"1"}
384 d = {b'mb': b"1", b'd': b"1"}
355 return parsedate(date, extendeddateformats, d)[0]
385 return parsedate(date, extendeddateformats, d)[0]
356
386
357 def upper(date: bytes) -> float:
387 def upper(date: bytes) -> float:
358 d = {b'mb': b"12", b'HI': b"23", b'M': b"59", b'S': b"59"}
388 d = {b'mb': b"12", b'HI': b"23", b'M': b"59", b'S': b"59"}
359 for days in (b"31", b"30", b"29"):
389 for days in (b"31", b"30", b"29"):
360 try:
390 try:
361 d[b"d"] = days
391 d[b"d"] = days
362 return parsedate(date, extendeddateformats, d)[0]
392 return parsedate(date, extendeddateformats, d)[0]
363 except error.ParseError:
393 except error.ParseError:
364 pass
394 pass
365 d[b"d"] = b"28"
395 d[b"d"] = b"28"
366 return parsedate(date, extendeddateformats, d)[0]
396 return parsedate(date, extendeddateformats, d)[0]
367
397
368 date = date.strip()
398 date = date.strip()
369
399
370 if not date:
400 if not date:
371 raise error.InputError(
401 raise error.InputError(
372 _(b"dates cannot consist entirely of whitespace")
402 _(b"dates cannot consist entirely of whitespace")
373 )
403 )
374 elif date[0:1] == b"<":
404 elif date[0:1] == b"<":
375 if not date[1:]:
405 if not date[1:]:
376 raise error.InputError(_(b"invalid day spec, use '<DATE'"))
406 raise error.InputError(_(b"invalid day spec, use '<DATE'"))
377 when = upper(date[1:])
407 when = upper(date[1:])
378 return lambda x: x <= when
408 return lambda x: x <= when
379 elif date[0:1] == b">":
409 elif date[0:1] == b">":
380 if not date[1:]:
410 if not date[1:]:
381 raise error.InputError(_(b"invalid day spec, use '>DATE'"))
411 raise error.InputError(_(b"invalid day spec, use '>DATE'"))
382 when = lower(date[1:])
412 when = lower(date[1:])
383 return lambda x: x >= when
413 return lambda x: x >= when
384 elif date[0:1] == b"-":
414 elif date[0:1] == b"-":
385 try:
415 try:
386 days = int(date[1:])
416 days = int(date[1:])
387 except ValueError:
417 except ValueError:
388 raise error.InputError(_(b"invalid day spec: %s") % date[1:])
418 raise error.InputError(_(b"invalid day spec: %s") % date[1:])
389 if days < 0:
419 if days < 0:
390 raise error.InputError(
420 raise error.InputError(
391 _(b"%s must be nonnegative (see 'hg help dates')") % date[1:]
421 _(b"%s must be nonnegative (see 'hg help dates')") % date[1:]
392 )
422 )
393 when = makedate()[0] - days * 3600 * 24
423 when = makedate()[0] - days * 3600 * 24
394 return lambda x: x >= when
424 return lambda x: x >= when
395 elif b" to " in date:
425 elif b" to " in date:
396 a, b = date.split(b" to ")
426 a, b = date.split(b" to ")
397 start, stop = lower(a), upper(b)
427 start, stop = lower(a), upper(b)
398 return lambda x: x >= start and x <= stop
428 return lambda x: x >= start and x <= stop
399 else:
429 else:
400 start, stop = lower(date), upper(date)
430 start, stop = lower(date), upper(date)
401 return lambda x: x >= start and x <= stop
431 return lambda x: x >= start and x <= stop
General Comments 0
You need to be logged in to leave comments. Login now