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