blob: ae153d8569ce59ce4e300cdbbd83894e0901f4b0 [file]
# SPDX-License-Identifier: GPL-2.0
import datetime
import subprocess
def parse_date_diff(date_str, now):
# format: {-,+}[0-9]+ {days,hours,minutes}
# e.g., -2 days, +3 days, +1 hours
fields = date_str.split()
if len(fields) != 2:
return None
nr, unit = fields
try:
nr = int(nr)
except:
return None
if not unit in ['days', 'hours', 'minutes']:
return None
if unit == 'days':
timedelta = datetime.timedelta(days=nr)
elif unit == 'hours':
timedelta = datetime.timedelta(hours=nr)
elif unit == 'minutes':
timedelta = datetime.timedelta(minutes=nr)
return now + timedelta
def parse_date(date_str):
now = datetime.datetime.now().astimezone()
the_date = parse_date_diff(date_str, now)
if the_date is not None:
return the_date, None
for s in ['-', ':', '/']:
date_str = date_str.replace(s, ' ')
fields = date_str.split()
if not len(fields) in [5, 3, 2, 1]:
return None, 'unexpected number of fields (%d)' % len(fields)
if fields[0] == 'yesterday':
yesterday = now - datetime.timedelta(1)
fields = [yesterday.year, yesterday.month, yesterday.day] + fields[1:]
try:
numbers = [int(x) for x in fields]
except ValueError as e:
return None, '%s' % e
if not len(numbers) in [5, 3, 2]:
# 5: year month day hour minute
# 3: year month day
# 2: hour minute
return None, 'only 5, 3, or 2 numbers are supported date input'
if len(numbers) == 2:
numbers = [now.year, now.month, now.day] + numbers
try:
return datetime.datetime(*numbers).astimezone(), None
except Exception as e:
return None, '%s' % e
def parse_iso_date(date):
try:
return datetime.datetime.fromisoformat(date).astimezone()
except:
# maybe lower version of python.
# the input 'date' may have UTC offset with hour separator, like
# '+05:00', while strptime() '%z' expoects no such separator. Make
# it compatible.
date = '%s%s' % (date[:-3], date[-2:])
return datetime.datetime.strptime(
date, '%Y-%m-%dT%H:%M:%S%z').astimezone()
def commit_date(commit):
try:
text = subprocess.check_output(
['git', 'log', '-1', commit, '--pretty=%cd',
'--date=iso-strict'],
stderr=subprocess.DEVNULL).decode().strip()
except Exception as e:
return None, 'git log %s fail (%s)' % (commit, e)
try:
return parse_iso_date(text), None
except Exception as e:
return None, 'parsing date (%s) fail (%s)' % (text, e)
def parse_date_arg_non_commit(tokens):
try:
date_str = ' '.join(tokens)
except Exception as e:
return None, 'tokens to string conversion fail (%s)' % e
return parse_date(date_str)
def parse_date_arg(tokens):
parsed, err = parse_date_arg_non_commit(tokens)
if err is None:
return parsed, err
if len(tokens) != 1:
return parsed, err
parsed, err2 = commit_date(tokens[0])
if err2 != None:
err = 'parsing date argument fail (%s, %s)' % (err2, err)
else:
err = None
return parsed, err
def date_format_description():
return ' '.join([
'For date argument, following formats are supported/.',
'Relative date ({-,+}[0-9]+ {days,hours,minutes}), e.g., -3 days.',
'Explicit date ("YYYY MM DD", "YYYY MM DD HH MM", or "HH MM").',
'"-", "/", ":" on explicit daste input are treated as space.',
'\'yesterday\'.',
'Commit (the commit date), e.g., v6.14).'
])
def add_date_arg(parser, option_name, help_msg):
format_msg = date_format_description()
if parser.epilog is None:
parser.epilog = format_msg
elif parser.epilog.find(format_msg) == -1:
parser.epilog += format_msg
help_msg += ' Format: show end of this message.'
parser.add_argument(
option_name, metavar='<date token>', nargs='+',
help=help_msg)