Mercurial > public > mercurial-scm > hg
diff mercurial/util.py @ 36607:c6061cadb400
util: extract all date-related utils in utils/dateutil module
With this commit, util.py lose 262 lines
Note for extensions author, if this commit breaks your extension, you can pull
the step-by-step split here to help you more easily pinpoint the renaming that
broke your extension:
hg pull https://bitbucket.org/octobus/mercurial-devel/ -r ac1f6453010d
Differential Revision: https://phab.mercurial-scm.org/D2282
author | Boris Feld <boris.feld@octobus.net> |
---|---|
date | Thu, 15 Feb 2018 17:18:26 +0100 |
parents | 281f66777ff0 |
children | c98d1c6763a6 |
line wrap: on
line diff
--- a/mercurial/util.py Thu Feb 08 23:27:24 2018 +0530 +++ b/mercurial/util.py Thu Feb 15 17:18:26 2018 +0100 @@ -17,11 +17,9 @@ import abc import bz2 -import calendar import codecs import collections import contextlib -import datetime import errno import gc import hashlib @@ -55,6 +53,7 @@ pycompat, urllibcompat, ) +from .utils import dateutil base85 = policy.importmod(r'base85') osutil = policy.importmod(r'osutil') @@ -855,48 +854,6 @@ if n == 4: return (vints[0], vints[1], vints[2], extra) -# used by parsedate -defaultdateformats = ( - '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601 - '%Y-%m-%dT%H:%M', # without seconds - '%Y-%m-%dT%H%M%S', # another awful but legal variant without : - '%Y-%m-%dT%H%M', # without seconds - '%Y-%m-%d %H:%M:%S', # our common legal variant - '%Y-%m-%d %H:%M', # without seconds - '%Y-%m-%d %H%M%S', # without : - '%Y-%m-%d %H%M', # without seconds - '%Y-%m-%d %I:%M:%S%p', - '%Y-%m-%d %H:%M', - '%Y-%m-%d %I:%M%p', - '%Y-%m-%d', - '%m-%d', - '%m/%d', - '%m/%d/%y', - '%m/%d/%Y', - '%a %b %d %H:%M:%S %Y', - '%a %b %d %I:%M:%S%p %Y', - '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822" - '%b %d %H:%M:%S %Y', - '%b %d %I:%M:%S%p %Y', - '%b %d %H:%M:%S', - '%b %d %I:%M:%S%p', - '%b %d %H:%M', - '%b %d %I:%M%p', - '%b %d %Y', - '%b %d', - '%H:%M:%S', - '%I:%M:%S%p', - '%H:%M', - '%I:%M%p', -) - -extendeddateformats = defaultdateformats + ( - "%Y", - "%Y-%m", - "%b", - "%b %Y", - ) - def cachefunc(func): '''cache the result of function calls''' # XXX doesn't handle keywords args @@ -2304,277 +2261,6 @@ return data -def makedate(timestamp=None): - '''Return a unix timestamp (or the current time) as a (unixtime, - offset) tuple based off the local timezone.''' - if timestamp is None: - timestamp = time.time() - if timestamp < 0: - hint = _("check your clock") - raise Abort(_("negative timestamp: %d") % timestamp, hint=hint) - delta = (datetime.datetime.utcfromtimestamp(timestamp) - - datetime.datetime.fromtimestamp(timestamp)) - tz = delta.days * 86400 + delta.seconds - return timestamp, tz - -def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'): - """represent a (unixtime, offset) tuple as a localized time. - unixtime is seconds since the epoch, and offset is the time zone's - number of seconds away from UTC. - - >>> datestr((0, 0)) - 'Thu Jan 01 00:00:00 1970 +0000' - >>> datestr((42, 0)) - 'Thu Jan 01 00:00:42 1970 +0000' - >>> datestr((-42, 0)) - 'Wed Dec 31 23:59:18 1969 +0000' - >>> datestr((0x7fffffff, 0)) - 'Tue Jan 19 03:14:07 2038 +0000' - >>> datestr((-0x80000000, 0)) - 'Fri Dec 13 20:45:52 1901 +0000' - """ - t, tz = date or makedate() - if "%1" in format or "%2" in format or "%z" in format: - sign = (tz > 0) and "-" or "+" - minutes = abs(tz) // 60 - q, r = divmod(minutes, 60) - format = format.replace("%z", "%1%2") - format = format.replace("%1", "%c%02d" % (sign, q)) - format = format.replace("%2", "%02d" % r) - d = t - tz - if d > 0x7fffffff: - d = 0x7fffffff - elif d < -0x80000000: - d = -0x80000000 - # Never use time.gmtime() and datetime.datetime.fromtimestamp() - # because they use the gmtime() system call which is buggy on Windows - # for negative values. - t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d) - s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format))) - return s - -def shortdate(date=None): - """turn (timestamp, tzoff) tuple into iso 8631 date.""" - return datestr(date, format='%Y-%m-%d') - -def parsetimezone(s): - """find a trailing timezone, if any, in string, and return a - (offset, remainder) pair""" - s = pycompat.bytestr(s) - - if s.endswith("GMT") or s.endswith("UTC"): - return 0, s[:-3].rstrip() - - # Unix-style timezones [+-]hhmm - if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit(): - sign = (s[-5] == "+") and 1 or -1 - hours = int(s[-4:-2]) - minutes = int(s[-2:]) - return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip() - - # ISO8601 trailing Z - if s.endswith("Z") and s[-2:-1].isdigit(): - return 0, s[:-1] - - # ISO8601-style [+-]hh:mm - if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and - s[-5:-3].isdigit() and s[-2:].isdigit()): - sign = (s[-6] == "+") and 1 or -1 - hours = int(s[-5:-3]) - minutes = int(s[-2:]) - return -sign * (hours * 60 + minutes) * 60, s[:-6] - - return None, s - -def strdate(string, format, defaults=None): - """parse a localized time string and return a (unixtime, offset) tuple. - if the string cannot be parsed, ValueError is raised.""" - if defaults is None: - defaults = {} - - # NOTE: unixtime = localunixtime + offset - offset, date = parsetimezone(string) - - # add missing elements from defaults - usenow = False # default to using biased defaults - for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity - part = pycompat.bytestr(part) - found = [True for p in part if ("%"+p) in format] - if not found: - date += "@" + defaults[part][usenow] - format += "@%" + part[0] - else: - # We've found a specific time element, less specific time - # elements are relative to today - usenow = True - - timetuple = time.strptime(encoding.strfromlocal(date), - encoding.strfromlocal(format)) - localunixtime = int(calendar.timegm(timetuple)) - if offset is None: - # local timezone - unixtime = int(time.mktime(timetuple)) - offset = unixtime - localunixtime - else: - unixtime = localunixtime + offset - return unixtime, offset - -def parsedate(date, formats=None, bias=None): - """parse a localized date/time and return a (unixtime, offset) tuple. - - The date may be a "unixtime offset" string or in one of the specified - formats. If the date already is a (unixtime, offset) tuple, it is returned. - - >>> parsedate(b' today ') == parsedate( - ... datetime.date.today().strftime('%b %d').encode('ascii')) - True - >>> parsedate(b'yesterday ') == parsedate( - ... (datetime.date.today() - datetime.timedelta(days=1) - ... ).strftime('%b %d').encode('ascii')) - True - >>> now, tz = makedate() - >>> strnow, strtz = parsedate(b'now') - >>> (strnow - now) < 1 - True - >>> tz == strtz - True - """ - if bias is None: - bias = {} - if not date: - return 0, 0 - if isinstance(date, tuple) and len(date) == 2: - return date - if not formats: - formats = defaultdateformats - date = date.strip() - - if date == 'now' or date == _('now'): - return makedate() - if date == 'today' or date == _('today'): - date = datetime.date.today().strftime(r'%b %d') - date = encoding.strtolocal(date) - elif date == 'yesterday' or date == _('yesterday'): - date = (datetime.date.today() - - datetime.timedelta(days=1)).strftime(r'%b %d') - date = encoding.strtolocal(date) - - try: - when, offset = map(int, date.split(' ')) - except ValueError: - # fill out defaults - now = makedate() - defaults = {} - for part in ("d", "mb", "yY", "HI", "M", "S"): - # this piece is for rounding the specific end of unknowns - b = bias.get(part) - if b is None: - if part[0:1] in "HMS": - b = "00" - else: - b = "0" - - # this piece is for matching the generic end to today's date - n = datestr(now, "%" + part[0:1]) - - defaults[part] = (b, n) - - for format in formats: - try: - when, offset = strdate(date, format, defaults) - except (ValueError, OverflowError): - pass - else: - break - else: - raise error.ParseError( - _('invalid date: %r') % pycompat.bytestr(date)) - # validate explicit (probably user-specified) date and - # time zone offset. values must fit in signed 32 bits for - # current 32-bit linux runtimes. timezones go from UTC-12 - # to UTC+14 - if when < -0x80000000 or when > 0x7fffffff: - raise error.ParseError(_('date exceeds 32 bits: %d') % when) - if offset < -50400 or offset > 43200: - raise error.ParseError(_('impossible time zone offset: %d') % offset) - return when, offset - -def matchdate(date): - """Return a function that matches a given date match specifier - - Formats include: - - '{date}' match a given date to the accuracy provided - - '<{date}' on or before a given date - - '>{date}' on or after a given date - - >>> p1 = parsedate(b"10:29:59") - >>> p2 = parsedate(b"10:30:00") - >>> p3 = parsedate(b"10:30:59") - >>> p4 = parsedate(b"10:31:00") - >>> p5 = parsedate(b"Sep 15 10:30:00 1999") - >>> f = matchdate(b"10:30") - >>> f(p1[0]) - False - >>> f(p2[0]) - True - >>> f(p3[0]) - True - >>> f(p4[0]) - False - >>> f(p5[0]) - False - """ - - def lower(date): - d = {'mb': "1", 'd': "1"} - return parsedate(date, extendeddateformats, d)[0] - - def upper(date): - d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"} - for days in ("31", "30", "29"): - try: - d["d"] = days - return parsedate(date, extendeddateformats, d)[0] - except error.ParseError: - pass - d["d"] = "28" - return parsedate(date, extendeddateformats, d)[0] - - date = date.strip() - - if not date: - raise Abort(_("dates cannot consist entirely of whitespace")) - elif date[0] == "<": - if not date[1:]: - raise Abort(_("invalid day spec, use '<DATE'")) - when = upper(date[1:]) - return lambda x: x <= when - elif date[0] == ">": - if not date[1:]: - raise Abort(_("invalid day spec, use '>DATE'")) - when = lower(date[1:]) - return lambda x: x >= when - elif date[0] == "-": - try: - days = int(date[1:]) - except ValueError: - raise Abort(_("invalid day spec: %s") % date[1:]) - if days < 0: - raise Abort(_("%s must be nonnegative (see 'hg help dates')") - % date[1:]) - when = makedate()[0] - days * 3600 * 24 - return lambda x: x >= when - elif " to " in date: - a, b = date.split(" to ") - start, stop = lower(a), upper(b) - return lambda x: x >= start and x <= stop - else: - start, stop = lower(date), upper(date) - return lambda x: x >= start and x <= stop - def stringmatcher(pattern, casesensitive=True): """ accepts a string, possibly starting with 're:' or 'literal:' prefix. @@ -4303,3 +3989,54 @@ if not (byte & 0x80): return result shift += 7 + +### +# Deprecation warnings for util.py splitting +### + +defaultdateformats = dateutil.defaultdateformats + +extendeddateformats = dateutil.extendeddateformats + +def makedate(*args, **kwargs): + msg = ("'util.makedate' is deprecated, " + "use 'utils.dateutil.makedate'") + nouideprecwarn(msg, "4.6") + return dateutil.makedate(*args, **kwargs) + +def datestr(*args, **kwargs): + msg = ("'util.datestr' is deprecated, " + "use 'utils.dateutil.datestr'") + nouideprecwarn(msg, "4.6") + debugstacktrace() + return dateutil.datestr(*args, **kwargs) + +def shortdate(*args, **kwargs): + msg = ("'util.shortdate' is deprecated, " + "use 'utils.dateutil.shortdate'") + nouideprecwarn(msg, "4.6") + return dateutil.shortdate(*args, **kwargs) + +def parsetimezone(*args, **kwargs): + msg = ("'util.parsetimezone' is deprecated, " + "use 'utils.dateutil.parsetimezone'") + nouideprecwarn(msg, "4.6") + return dateutil.parsetimezone(*args, **kwargs) + +def strdate(*args, **kwargs): + msg = ("'util.strdate' is deprecated, " + "use 'utils.dateutil.strdate'") + nouideprecwarn(msg, "4.6") + return dateutil.strdate(*args, **kwargs) + +def parsedate(*args, **kwargs): + msg = ("'util.parsedate' is deprecated, " + "use 'utils.dateutil.parsedate'") + nouideprecwarn(msg, "4.6") + return dateutil.parsedate(*args, **kwargs) + +def matchdate(*args, **kwargs): + msg = ("'util.matchdate' is deprecated, " + "use 'utils.dateutil.matchdate'") + nouideprecwarn(msg, "4.6") + return dateutil.matchdate(*args, **kwargs)