##// END OF EJS Templates
lock: properly convert error to bytes...
lock: properly convert error to bytes Flagged by pytype when a later changeset is applied moving typing comment to annotation. We fix this ahead of the annotation change to make sure pytype remains happy after the change. We have to do fairly crazy dance for pytype to be happy. This probably comes from the fact IOError.filename probably claims to be `str` while it is actually `bytes` if the filename raising that `IOError` is bytes. At the same time, `IOError.strerror` is consistently `str` and should be passed as `str` everywhere.

File last commit:

r52178:9d372155 default
r52179:81224afd default
Show More
dateutil.py
399 lines | 11.6 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.
import calendar
import datetime
import time
pytype: import typing directly...
r52178 from typing import (
Callable,
Dict,
Iterable,
Optional,
Tuple,
Union,
)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 from ..i18n import _
from .. import (
encoding,
error,
pycompat,
)
pytype: import typing directly...
r52178 # keeps pyflakes happy
assert [
Callable,
Dict,
Iterable,
Optional,
Tuple,
Union,
]
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387
pytype: import typing directly...
r52178 hgdate = Tuple[float, int] # (unixtime, offset)
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 # 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):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (Optional[float]) -> hgdate
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
)
Mads Kiilerich
utils: stop using datetime.utcfromtimestamp() deprecated in Python 3.12...
r51645 tz = round(
Augie Fackler
formatting: blacken the codebase...
r43346 timestamp
Mads Kiilerich
utils: stop using datetime.utcfromtimestamp() deprecated in Python 3.12...
r51645 - datetime.datetime.fromtimestamp(
timestamp,
)
.replace(tzinfo=datetime.timezone.utc)
.timestamp()
)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 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'):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (Optional[hgdate], bytes) -> bytes
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):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (Optional[hgdate]) -> bytes
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 """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):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (bytes) -> Tuple[Optional[int], bytes]
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 """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):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (bytes, bytes, Optional[Dict[bytes, Tuple[bytes, bytes]]]) -> hgdate
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 """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):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (Union[bytes, hgdate], Optional[Iterable[bytes]], Optional[Dict[bytes, bytes]]) -> hgdate
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 """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
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 if isinstance(date, tuple):
if len(date) == 2:
return date
else:
raise error.ProgrammingError(b"invalid date format")
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 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):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (bytes) -> Callable[[float], bool]
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 """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):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (bytes) -> float
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):
Matt Harbison
typing: add type annotations to mercurial/utils/dateutil.py...
r47387 # type: (bytes) -> float
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