##// END OF EJS Templates
tests: add a comment in a test that will hopefully save someone some time...
tests: add a comment in a test that will hopefully save someone some time I spent at least an hour, probably closer to 1.5, trying to figure out what this was complaining about. Hopefully anyone else in my position will see this note and not waste the time. Differential Revision: https://phab.mercurial-scm.org/D9953

File last commit:

r47147:6894c9ef default
r47261:02d91167 default
Show More
dateutil.py
363 lines | 10.8 KiB | text/x-python | PythonLexer
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 # util.py - Mercurial utility functions relative to dates
#
# Copyright 2018 Boris Feld <boris.feld@octobus.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import calendar
import datetime
import time
from ..i18n import _
from .. import (
encoding,
error,
pycompat,
)
# used by parsedate
defaultdateformats = (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
b'%Y-%m-%dT%H:%M', # without seconds
b'%Y-%m-%dT%H%M%S', # another awful but legal variant without :
b'%Y-%m-%dT%H%M', # without seconds
b'%Y-%m-%d %H:%M:%S', # our common legal variant
b'%Y-%m-%d %H:%M', # without seconds
b'%Y-%m-%d %H%M%S', # without :
b'%Y-%m-%d %H%M', # without seconds
b'%Y-%m-%d %I:%M:%S%p',
b'%Y-%m-%d %H:%M',
b'%Y-%m-%d %I:%M%p',
b'%Y-%m-%d',
b'%m-%d',
b'%m/%d',
b'%m/%d/%y',
b'%m/%d/%Y',
b'%a %b %d %H:%M:%S %Y',
b'%a %b %d %I:%M:%S%p %Y',
b'%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
b'%b %d %H:%M:%S %Y',
b'%b %d %I:%M:%S%p %Y',
b'%b %d %H:%M:%S',
b'%b %d %I:%M:%S%p',
b'%b %d %H:%M',
b'%b %d %I:%M%p',
b'%b %d %Y',
b'%b %d',
b'%H:%M:%S',
b'%I:%M:%S%p',
b'%H:%M',
b'%I:%M%p',
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 )
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 extendeddateformats = defaultdateformats + (
b"%Y",
b"%Y-%m",
b"%b",
b"%b %Y",
)
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625
def makedate(timestamp=None):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """Return a unix timestamp (or the current time) as a (unixtime,
offset) tuple based off the local timezone."""
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if timestamp is None:
timestamp = time.time()
if timestamp < 0:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 hint = _(b"check your clock")
Martin von Zweigbergk
errors: use InputError for incorrectly formatted dates...
r47147 raise error.InputError(
_(b"negative timestamp: %d") % timestamp, hint=hint
)
Augie Fackler
formatting: blacken the codebase...
r43346 delta = datetime.datetime.utcfromtimestamp(
timestamp
) - datetime.datetime.fromtimestamp(timestamp)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 tz = delta.days * 86400 + delta.seconds
return timestamp, tz
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 def datestr(date=None, format=b'%a %b %d %H:%M:%S %Y %1%2'):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 """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()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b"%1" in format or b"%2" in format or b"%z" in format:
sign = (tz > 0) and b"-" or b"+"
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 minutes = abs(tz) // 60
q, r = divmod(minutes, 60)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 format = format.replace(b"%z", b"%1%2")
format = format.replace(b"%1", b"%c%02d" % (sign, q))
format = format.replace(b"%2", b"%02d" % r)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 d = t - tz
Augie Fackler
formatting: blacken the codebase...
r43346 if d > 0x7FFFFFFF:
d = 0x7FFFFFFF
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 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
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 def shortdate(date=None):
"""turn (timestamp, tzoff) tuple into iso 8631 date."""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return datestr(date, format=b'%Y-%m-%d')
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 def parsetimezone(s):
"""find a trailing timezone, if any, in string, and return a
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 (offset, remainder) pair"""
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 s = pycompat.bytestr(s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if s.endswith(b"GMT") or s.endswith(b"UTC"):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return 0, s[:-3].rstrip()
# Unix-style timezones [+-]hhmm
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if len(s) >= 5 and s[-5] in b"+-" and s[-4:].isdigit():
sign = (s[-5] == b"+") and 1 or -1
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 hours = int(s[-4:-2])
minutes = int(s[-2:])
return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
# ISO8601 trailing Z
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if s.endswith(b"Z") and s[-2:-1].isdigit():
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return 0, s[:-1]
# ISO8601-style [+-]hh:mm
Augie Fackler
formatting: blacken the codebase...
r43346 if (
len(s) >= 6
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 and s[-6] in b"+-"
and s[-3] == b":"
Augie Fackler
formatting: blacken the codebase...
r43346 and s[-5:-3].isdigit()
and s[-2:].isdigit()
):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 sign = (s[-6] == b"+") and 1 or -1
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 hours = int(s[-5:-3])
minutes = int(s[-2:])
return -sign * (hours * 60 + minutes) * 60, s[:-6]
return None, s
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 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
Augie Fackler
formatting: blacken the codebase...
r43346 usenow = False # default to using biased defaults
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for part in (
b"S",
b"M",
b"HI",
b"d",
b"mb",
b"yY",
): # decreasing specificity
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 part = pycompat.bytestr(part)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 found = [True for p in part if (b"%" + p) in format]
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if not found:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 date += b"@" + defaults[part][usenow]
format += b"@%" + part[0]
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 else:
# We've found a specific time element, less specific time
# elements are relative to today
usenow = True
Augie Fackler
formatting: blacken the codebase...
r43346 timetuple = time.strptime(
encoding.strfromlocal(date), encoding.strfromlocal(format)
)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 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
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 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
Jun Wu
dateutil: correct default for Ymd in parsedate...
r44220 >>> parsedate(b'2000 UTC', formats=extendeddateformats)
(946684800, 0)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 """
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()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if date == b'now' or date == _(b'now'):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return makedate()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if date == b'today' or date == _(b'today'):
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 date = datetime.date.today().strftime('%b %d')
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 date = encoding.strtolocal(date)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif date == b'yesterday' or date == _(b'yesterday'):
Augie Fackler
formatting: blacken the codebase...
r43346 date = (datetime.date.today() - datetime.timedelta(days=1)).strftime(
r'%b %d'
)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 date = encoding.strtolocal(date)
try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 when, offset = map(int, date.split(b' '))
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 except ValueError:
# fill out defaults
now = makedate()
defaults = {}
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for part in (b"d", b"mb", b"yY", b"HI", b"M", b"S"):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 # this piece is for rounding the specific end of unknowns
b = bias.get(part)
if b is None:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if part[0:1] in b"HMS":
b = b"00"
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 else:
Jun Wu
dateutil: correct default for Ymd in parsedate...
r44220 # year, month, and day start from 1
b = b"1"
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625
# this piece is for matching the generic end to today's date
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 n = datestr(now, b"%" + part[0:1])
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625
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(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'invalid date: %r') % pycompat.bytestr(date)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 # 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
Augie Fackler
formatting: blacken the codebase...
r43346 if when < -0x80000000 or when > 0x7FFFFFFF:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'date exceeds 32 bits: %d') % when)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if offset < -50400 or offset > 43200:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'impossible time zone offset: %d') % offset)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return when, offset
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 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):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 d = {b'mb': b"1", b'd': b"1"}
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return parsedate(date, extendeddateformats, d)[0]
def upper(date):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 d = {b'mb': b"12", b'HI': b"23", b'M': b"59", b'S': b"59"}
for days in (b"31", b"30", b"29"):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 d[b"d"] = days
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return parsedate(date, extendeddateformats, d)[0]
except error.ParseError:
pass
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 d[b"d"] = b"28"
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return parsedate(date, extendeddateformats, d)[0]
date = date.strip()
if not date:
Martin von Zweigbergk
errors: use InputError for incorrectly formatted dates...
r47147 raise error.InputError(
_(b"dates cannot consist entirely of whitespace")
)
Mark Thomas
py3: fix test-parse-date.t...
r40292 elif date[0:1] == b"<":
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if not date[1:]:
Martin von Zweigbergk
errors: use InputError for incorrectly formatted dates...
r47147 raise error.InputError(_(b"invalid day spec, use '<DATE'"))
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 when = upper(date[1:])
return lambda x: x <= when
Mark Thomas
py3: fix test-parse-date.t...
r40292 elif date[0:1] == b">":
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if not date[1:]:
Martin von Zweigbergk
errors: use InputError for incorrectly formatted dates...
r47147 raise error.InputError(_(b"invalid day spec, use '>DATE'"))
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 when = lower(date[1:])
return lambda x: x >= when
Mark Thomas
py3: fix test-parse-date.t...
r40292 elif date[0:1] == b"-":
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 try:
days = int(date[1:])
except ValueError:
Martin von Zweigbergk
errors: use InputError for incorrectly formatted dates...
r47147 raise error.InputError(_(b"invalid day spec: %s") % date[1:])
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if days < 0:
Martin von Zweigbergk
errors: use InputError for incorrectly formatted dates...
r47147 raise error.InputError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b"%s must be nonnegative (see 'hg help dates')") % date[1:]
Augie Fackler
formatting: blacken the codebase...
r43346 )
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 when = makedate()[0] - days * 3600 * 24
return lambda x: x >= when
Mark Thomas
py3: fix test-parse-date.t...
r40292 elif b" to " in date:
a, b = date.split(b" to ")
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 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