Skip to content

Commit

Permalink
Simplified regex for pretty date strings. Added unit tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
alalazo committed Mar 20, 2018
1 parent 77c7f76 commit 7360e02
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 26 deletions.
40 changes: 23 additions & 17 deletions lib/spack/llnl/util/lang.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ def pretty_date(time, now=None):
return str(diff) + " years ago"


def str2date(date_str):
def pretty_string_to_date(date_str, now=None):
"""Parses a string representing a date and returns a datetime object.
Args:
Expand All @@ -456,6 +456,8 @@ def str2date(date_str):

pattern = {}

now = now or datetime.now()

# datetime formats
pattern[re.compile('^\d{4}$')] = lambda x: datetime.strptime(x, '%Y')
pattern[re.compile('^\d{4}-\d{2}$')] = lambda x: datetime.strptime(
Expand All @@ -465,33 +467,37 @@ def str2date(date_str):
x, '%Y-%m-%d'
)

# yesterday
callback = lambda x: datetime.now() - timedelta(days=1)
pattern[re.compile('^yesterday$')] = callback
pretty_regex = re.compile(
r'(a|\d+)\s*(year|month|week|day|hour|minute|second)s?\s*ago')

# {n} days ago
n_days_regexp = re.compile('^(\d*) day[s]? ago')
def _n_xxx_ago(x):
how_many, time_period = pretty_regex.search(x).groups()

def _n_days_ago(x):
ndays = n_days_regexp.search(x).group(1)
return datetime.now() - timedelta(days=int(ndays))
how_many = 1 if how_many == 'a' else int(how_many)

pattern[n_days_regexp] = _n_days_ago
# timedelta natively supports time periods up to 'weeks'.
# To apply month or year we convert to 30 and 365 days
if time_period == 'month':
how_many *= 30
time_period = 'day'
elif time_period == 'year':
how_many *= 365
time_period = 'day'

# {n} weeks ago
n_weeks_regexp = re.compile('^(\d*) week[s]? ago')
kwargs = {(time_period + 's'): how_many}
return now - timedelta(**kwargs)

def _n_weeks_ago(x):
nweeks = n_weeks_regexp.search(x).group(1)
return datetime.now() - timedelta(weeks=int(nweeks))
pattern[pretty_regex] = _n_xxx_ago

pattern[n_weeks_regexp] = _n_weeks_ago
# yesterday
callback = lambda x: now - timedelta(days=1)
pattern[re.compile('^yesterday$')] = callback

for regexp, parser in pattern.items():
if bool(regexp.match(date_str)):
return parser(date_str)

msg = 'date {0} does not match any valid format'.format(date_str)
msg = 'date "{0}" does not match any valid format'.format(date_str)
raise ValueError(msg)


Expand Down
2 changes: 1 addition & 1 deletion lib/spack/spack/cmd/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def query_arguments(args):
for attribute in ('start_date', 'end_date'):
date = getattr(args, attribute)
if date:
q_args[attribute] = llnl.util.lang.str2date(date)
q_args[attribute] = llnl.util.lang.pretty_string_to_date(date)

return q_args

Expand Down
18 changes: 10 additions & 8 deletions lib/spack/spack/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ def _read_suppress_error():
tty.debug(
'RECONSTRUCTING FROM SPEC.YAML: {0}'.format(spec))
explicit = True
inst_time = _now()
inst_time = os.stat(spec.prefix).st_ctime
if old_data is not None:
old_info = old_data.get(spec.dag_hash())
if old_info is not None:
Expand Down Expand Up @@ -611,7 +611,13 @@ def _read(self):
self._write(None, None, None)
self.reindex(spack.store.layout)

def _add(self, spec, directory_layout=None, **kwargs):
def _add(
self,
spec,
directory_layout=None,
explicit=False,
installation_time=None
):
"""Add an install record for this spec to the database.
Assumes spec is installed in ``layout.path_for_spec(spec)``.
Expand Down Expand Up @@ -641,10 +647,7 @@ def _add(self, spec, directory_layout=None, **kwargs):
"Specs added to DB must be concrete.")

# Retrieve optional arguments
explicit = kwargs.get('explicit', False)
installation_time = kwargs.get(
'installation_time', _now()
)
installation_time = installation_time or _now()

for dep in spec.dependencies(_tracked_deps):
dkey = dep.dag_hash()
Expand Down Expand Up @@ -923,8 +926,7 @@ def query_one(self, query_spec, known=any, installed=True):
"""
concrete_specs = self.query(
query_spec, known=known, installed=installed
)
query_spec, known=known, installed=installed)
assert len(concrete_specs) <= 1
return concrete_specs[0] if concrete_specs else None

Expand Down
33 changes: 33 additions & 0 deletions lib/spack/spack/test/llnl/util/lang.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import pytest

from datetime import datetime, timedelta

import llnl.util.lang
from llnl.util.lang import pretty_date, match_predicate


@pytest.fixture()
def now():
return datetime.now()


def test_pretty_date():
"""Make sure pretty_date prints the right dates."""
now = datetime.now()
Expand Down Expand Up @@ -75,6 +82,32 @@ def test_pretty_date():
assert pretty_date(years, now) == "2 years ago"


@pytest.mark.parametrize('delta,pretty_string', [
(timedelta(days=1), 'a day ago'),
(timedelta(days=1), 'yesterday'),
(timedelta(days=1), '1 day ago'),
(timedelta(weeks=1), '1 week ago'),
(timedelta(weeks=3), '3 weeks ago'),
(timedelta(days=30), '1 month ago'),
(timedelta(days=730), '2 years ago'),
])
def test_pretty_string_to_date_delta(now, delta, pretty_string):
t1 = now - delta
t2 = llnl.util.lang.pretty_string_to_date(pretty_string, now)
assert t1 == t2


@pytest.mark.parametrize('format,pretty_string', [
('%Y', '2018'),
('%Y-%m', '2015-03'),
('%Y-%m-%d', '2015-03-28'),
])
def test_pretty_string_to_date(format, pretty_string):
t1 = datetime.strptime(pretty_string, format)
t2 = llnl.util.lang.pretty_string_to_date(pretty_string, now)
assert t1 == t2


def test_match_predicate():
matcher = match_predicate(lambda x: True)
assert matcher('foo')
Expand Down

0 comments on commit 7360e02

Please sign in to comment.