# HG changeset patch # User Matt Mackall # Date 1165439504 21600 # Node ID bf6ab30559e6e6c6eb81ccec3ad865a2cd868780 # Parent 6fa11a9d7cac20a05753649b1e50be271271e1f6 Add date matching support Add extended date formats (eg "Dec", "2006") Add a couple missing basic date formats Improve default date element scheme to parsedate Add matchdate function to match a date spec Add -e switch and range matching to debugdate diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -788,11 +788,17 @@ def debugdata(ui, file_, rev): except KeyError: raise util.Abort(_('invalid revision identifier %s') % rev) -def debugdate(ui, date): +def debugdate(ui, date, range=None, **opts): """parse and display a date""" - d = util.parsedate(date) + if opts["extended"]: + d = util.parsedate(date, util.extendeddateformats) + else: + d = util.parsedate(date) ui.write("internal: %s %s\n" % d) ui.write("standard: %s\n" % util.datestr(d)) + if range: + m = util.matchdate(range) + ui.write("match: %s\n" % m(d[0])) def debugindex(ui, file_): """dump the contents of an index file""" @@ -2483,7 +2489,9 @@ table = { "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')), "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')), "debugstate": (debugstate, [], _('debugstate')), - "debugdate": (debugdate, [], _('debugdata DATE')), + "debugdate": (debugdate, + [('e','extended', None, _('try extended date formats'))], + _('debugdata [-e] DATE [RANGE]')), "debugdata": (debugdata, [], _('debugdata FILE REV')), "debugindex": (debugindex, [], _('debugindex FILE')), "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')), diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -85,6 +85,8 @@ defaultdateformats = ( '%a %b %d %H:%M:%S %Y', '%a %b %d %I:%M:%S%p %Y', '%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', @@ -96,6 +98,13 @@ defaultdateformats = ( '%I:%M%p', ) +extendeddateformats = defaultdateformats + ( + "%Y", + "%Y-%m", + "%b", + "%b %Y", + ) + class SignalInterrupt(Exception): """Exception raised on SIGTERM and SIGHUP.""" @@ -1058,7 +1067,7 @@ def datestr(date=None, format='%a %b %d s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60)) return s -def strdate(string, format='%a %b %d %H:%M:%S %Y'): +def strdate(string, format, defaults): """parse a localized time string and return a (unixtime, offset) tuple. if the string cannot be parsed, ValueError is raised.""" def timezone(string): @@ -1076,16 +1085,12 @@ def strdate(string, format='%a %b %d %H: if offset != None: date = " ".join(string.split()[:-1]) - # add missing elements - if '%y' not in format.lower(): - date += "@" + datestr(makedate(), "%Y", False) - format += "@%Y" - if '%m' not in format and '%b' not in format: - date += "@" + datestr(makedate(), "%m", False) - format += "@%m" - if '%d' not in format: - date += "@" + datestr(makedate(), "%d", False) - format += "@%d" + # add missing elements from defaults + for part in defaults: + found = [True for p in part if ("%"+p) in format] + if not found: + date += "@" + defaults[part] + format += "@%" + part[0] timetuple = time.strptime(date, format) localunixtime = int(calendar.timegm(timetuple)) @@ -1097,7 +1102,7 @@ def strdate(string, format='%a %b %d %H: unixtime = localunixtime + offset return unixtime, offset -def parsedate(string, formats=None): +def parsedate(string, formats=None, defaults=None): """parse a localized time string and return a (unixtime, offset) tuple. The date may be a "unixtime offset" string or in one of the specified formats.""" @@ -1109,9 +1114,22 @@ def parsedate(string, formats=None): try: when, offset = map(int, string.split(' ')) except ValueError: + # fill out defaults + if not defaults: + defaults = {} + now = makedate() + for part in "d mb yY HI M S".split(): + if part not in defaults: + if part[0] in "HMS": + defaults[part] = "00" + elif part[0] in "dm": + defaults[part] = "1" + else: + defaults[part] = datestr(now, "%" + part[0], False) + for format in formats: try: - when, offset = strdate(string, format) + when, offset = strdate(string, format, defaults) except ValueError: pass else: @@ -1128,6 +1146,54 @@ def parsedate(string, formats=None): raise Abort(_('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 + + """ + + def lower(date): + return parsedate(date, extendeddateformats)[0] + + def upper(date): + d = dict(mb="12", HI="23", M="59", S="59") + for days in "31 30 29".split(): + try: + d["d"] = days + return parsedate(date, extendeddateformats, d)[0] + except: + pass + d["d"] = "28" + return parsedate(date, extendeddateformats, d)[0] + + if date[0] == "<": + when = upper(date[1:]) + return lambda x: x <= when + elif date[0] == ">": + 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:]) + 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 shortuser(user): """Return a short representation of a user name or email address.""" f = user.find('@')