##// END OF EJS Templates
filemerge: drop a default argument to appease pytype...
filemerge: drop a default argument to appease pytype The function slices and takes the length of this argument without internally setting it if not provided. There was no bug here because both callers passed the argument. Differential Revision: https://phab.mercurial-scm.org/D7464

File last commit:

r44170:aef7b91d default
r44304:1ffbd03c default
Show More
dateutil.py
354 lines | 10.7 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
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 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):
'''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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 hint = _(b"check your clock")
raise error.Abort(_(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
(offset, remainder) pair"""
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(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:]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(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:]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"invalid day spec: %s") % date[1:])
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if days < 0:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
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