diff --git a/hgext/convert/convcmd.py b/hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py +++ b/hgext/convert/convcmd.py @@ -19,6 +19,7 @@ scmutil, util, ) +from mercurial.utils import dateutil from . import ( bzr, @@ -356,7 +357,7 @@ dates = {} def getdate(n): if n not in dates: - dates[n] = util.parsedate(self.commitcache[n].date) + dates[n] = dateutil.parsedate(self.commitcache[n].date) return dates[n] def picknext(nodes): diff --git a/hgext/convert/cvsps.py b/hgext/convert/cvsps.py --- a/hgext/convert/cvsps.py +++ b/hgext/convert/cvsps.py @@ -337,7 +337,7 @@ if len(d.split()) != 3: # cvs log dates always in GMT d = d + ' UTC' - e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S', + e.date = dateutil.parsedate(d, ['%y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S']) e.author = scache(match.group(2)) diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py --- a/hgext/convert/subversion.py +++ b/hgext/convert/subversion.py @@ -891,7 +891,7 @@ # Example SVN datetime. Includes microseconds. # ISO-8601 conformant # '2007-01-04T17:35:00.902377Z' - date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"]) + date = dateutil.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"]) if self.ui.configbool('convert', 'localtimezone'): date = makedatetimestamp(date[0]) diff --git a/hgext/fetch.py b/hgext/fetch.py --- a/hgext/fetch.py +++ b/hgext/fetch.py @@ -23,6 +23,7 @@ registrar, util, ) +from mercurial.utils import dateutil release = lock.release cmdtable = {} @@ -64,7 +65,7 @@ opts = pycompat.byteskwargs(opts) date = opts.get('date') if date: - opts['date'] = util.parsedate(date) + opts['date'] = dateutil.parsedate(date) parent, _p2 = repo.dirstate.parents() branch = repo.dirstate.branch() diff --git a/hgext/gpg.py b/hgext/gpg.py --- a/hgext/gpg.py +++ b/hgext/gpg.py @@ -21,6 +21,7 @@ registrar, util, ) +from mercurial.utils import dateutil cmdtable = {} command = registrar.command(cmdtable) @@ -259,7 +260,7 @@ date = opts.get('date') if date: - opts['date'] = util.parsedate(date) + opts['date'] = dateutil.parsedate(date) if revs: nodes = [repo.lookup(n) for n in revs] diff --git a/hgext/keyword.py b/hgext/keyword.py --- a/hgext/keyword.py +++ b/hgext/keyword.py @@ -156,7 +156,8 @@ def utcdate(text): '''Date. Returns a UTC-date in this format: "2009/08/18 11:00:13". ''' - return dateutil.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S') + dateformat = '%Y/%m/%d %H:%M:%S' + return dateutil.datestr((dateutil.parsedate(text)[0], 0), dateformat) # date like in svn's $Date @templatefilter('svnisodate') def svnisodate(text): @@ -170,7 +171,8 @@ '''Date. Returns a UTC-date in this format: "2009-08-18 11:00:13Z". ''' - return dateutil.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ') + dateformat = '%Y-%m-%d %H:%M:%SZ' + return dateutil.datestr((dateutil.parsedate(text)[0], 0), dateformat) # make keyword tools accessible kwtools = {'hgcmd': ''} diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -1200,7 +1200,7 @@ user = opts.get('user') date = opts.get('date') if date: - date = util.parsedate(date) + date = dateutil.parsedate(date) diffopts = self.diffopts({'git': opts.get('git')}, plain=True) if opts.get('checkname', True): self.checkpatchname(patchfn) @@ -1646,7 +1646,7 @@ newuser = opts.get('user') newdate = opts.get('date') if newdate: - newdate = '%d %d' % util.parsedate(newdate) + newdate = '%d %d' % dateutil.parsedate(newdate) wlock = repo.wlock() try: diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py --- a/hgext/patchbomb.py +++ b/hgext/patchbomb.py @@ -663,7 +663,7 @@ # start if date: - start_time = util.parsedate(date) + start_time = dateutil.parsedate(date) else: start_time = dateutil.makedate() diff --git a/mercurial/changelog.py b/mercurial/changelog.py --- a/mercurial/changelog.py +++ b/mercurial/changelog.py @@ -512,7 +512,7 @@ desc = stripdesc(desc) if date: - parseddate = "%d %d" % util.parsedate(date) + parseddate = "%d %d" % dateutil.parsedate(date) else: parseddate = "%d %d" % dateutil.makedate() if extra: diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -3118,7 +3118,7 @@ '''commit the specified files or all outstanding changes''' date = opts.get('date') if date: - opts['date'] = util.parsedate(date) + opts['date'] = dateutil.parsedate(date) message = logmessage(ui, opts) matcher = scmutil.match(repo[None], pats, opts) @@ -3183,7 +3183,7 @@ date = opts.get('date') or old.date() # Parse the date to allow comparison between date and old.date() - date = util.parsedate(date) + date = dateutil.parsedate(date) if len(old.parents()) > 1: # ctx.files() isn't reliable for merges, so fall back to the diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -584,7 +584,7 @@ date = opts.get('date') if date: - opts['date'] = util.parsedate(date) + opts['date'] = dateutil.parsedate(date) cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) @@ -3004,7 +3004,7 @@ date = opts.get('date') if date: - opts['date'] = util.parsedate(date) + opts['date'] = dateutil.parsedate(date) exact = opts.get('exact') update = not opts.get('bypass') @@ -5330,7 +5330,7 @@ date = opts.get('date') if date: - date = util.parsedate(date) + date = dateutil.parsedate(date) if opts.get('remove'): editform = 'tag.remove' diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -1331,7 +1331,7 @@ self._node = None self._text = text if date: - self._date = util.parsedate(date) + self._date = dateutil.parsedate(date) if user: self._user = user if changes: @@ -2448,7 +2448,7 @@ user receives the committer name and defaults to current repository username, date is the commit date in any format - supported by util.parsedate() and defaults to current date, extra + supported by dateutil.parsedate() and defaults to current date, extra is a dictionary of metadata or is left empty. """ @@ -2663,7 +2663,7 @@ user receives the committer name and defaults to current repository username, date is the commit date in any format supported by - util.parsedate() and defaults to current date, extra is a dictionary of + dateutil.parsedate() and defaults to current date, extra is a dictionary of metadata or is left empty. """ def __new__(cls, repo, originalctx, *args, **kwargs): diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -557,9 +557,9 @@ def debugdate(ui, date, range=None, **opts): """parse and display a date""" if opts[r"extended"]: - d = util.parsedate(date, util.extendeddateformats) + d = dateutil.parsedate(date, util.extendeddateformats) else: - d = util.parsedate(date) + d = dateutil.parsedate(date) ui.write(("internal: %s %s\n") % d) ui.write(("standard: %s\n") % dateutil.datestr(d)) if range: @@ -1574,7 +1574,7 @@ try: date = opts.get('date') if date: - date = util.parsedate(date) + date = dateutil.parsedate(date) else: date = None prec = parsenodeid(precursor) diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py --- a/mercurial/obsolete.py +++ b/mercurial/obsolete.py @@ -609,7 +609,7 @@ if date is None: if 'date' in metadata: # as a courtesy for out-of-tree extensions - date = util.parsedate(metadata.pop('date')) + date = dateutil.parsedate(metadata.pop('date')) elif ui is not None: date = ui.configdate('devel', 'default-date') if date is None: diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -905,7 +905,7 @@ date = evalfuncarg(context, mapping, args[0]) try: - date = util.parsedate(date) + date = dateutil.parsedate(date) except AttributeError: # not str nor date tuple # i18n: "localdate" is a keyword raise error.ParseError(_("localdate expects a date information")) diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -37,6 +37,7 @@ scmutil, util, ) +from .utils import dateutil urlreq = util.urlreq @@ -722,7 +723,7 @@ (0, 0) """ if self.config(section, name, default, untrusted): - return self.configwith(util.parsedate, section, name, default, + return self.configwith(dateutil.parsedate, section, name, default, 'date', untrusted) if default is _unset: return None diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -20,7 +20,6 @@ import codecs import collections import contextlib -import datetime import errno import gc import hashlib @@ -1905,85 +1904,6 @@ limit -= len(s) yield s -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 = dateutil.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 dateutil.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 = dateutil.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 = dateutil.datestr(now, "%" + part[0:1]) - - defaults[part] = (b, n) - - for format in formats: - try: - when, offset = dateutil.strdate(date, format, defaults) - except (ValueError, OverflowError): - pass - else: - break - else: - raise error.ParseError(_('invalid date: %r') % 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 @@ -1995,11 +1915,11 @@ '>{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") + >>> p1 = dateutil.parsedate(b"10:29:59") + >>> p2 = dateutil.parsedate(b"10:30:00") + >>> p3 = dateutil.parsedate(b"10:30:59") + >>> p4 = dateutil.parsedate(b"10:31:00") + >>> p5 = dateutil.parsedate(b"Sep 15 10:30:00 1999") >>> f = matchdate(b"10:30") >>> f(p1[0]) False @@ -2015,18 +1935,18 @@ def lower(date): d = {'mb': "1", 'd': "1"} - return parsedate(date, extendeddateformats, d)[0] + return dateutil.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] + return dateutil.parsedate(date, extendeddateformats, d)[0] except Abort: pass d["d"] = "28" - return parsedate(date, extendeddateformats, d)[0] + return dateutil.parsedate(date, extendeddateformats, d)[0] date = date.strip() @@ -3827,3 +3747,9 @@ 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) + diff --git a/mercurial/utils/dateutil.py b/mercurial/utils/dateutil.py --- a/mercurial/utils/dateutil.py +++ b/mercurial/utils/dateutil.py @@ -174,3 +174,82 @@ 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') % 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 + diff --git a/tests/fakedirstatewritetime.py b/tests/fakedirstatewritetime.py --- a/tests/fakedirstatewritetime.py +++ b/tests/fakedirstatewritetime.py @@ -13,8 +13,8 @@ extensions, policy, registrar, - util, ) +from mercurial.utils import dateutil configtable = {} configitem = registrar.configitem(configtable) @@ -49,7 +49,7 @@ # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy - fakenow = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0] + fakenow = dateutil.parsedate(fakenow, ['%Y%m%d%H%M'])[0] orig_pack_dirstate = parsers.pack_dirstate orig_dirstate_getfsnow = dirstate._getfsnow diff --git a/tests/fakepatchtime.py b/tests/fakepatchtime.py --- a/tests/fakepatchtime.py +++ b/tests/fakepatchtime.py @@ -7,8 +7,8 @@ extensions, patch as patchmod, registrar, - util, ) +from mercurial.utils import dateutil configtable = {} configitem = registrar.configitem(configtable) @@ -30,7 +30,7 @@ if fakenow: # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy - fakenow = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0] + fakenow = dateutil.parsedate(fakenow, ['%Y%m%d%H%M'])[0] for f in files: repo.wvfs.utime(f, (fakenow, fakenow))