From 97f0caf6174ccad41407abdcfc6f96f06ea71df8 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Thu, 24 Aug 2023 00:40:21 +0200 Subject: [PATCH] Parse date-/time-range expressions using the Aika library https://github.com/panodata/aika --- .github/workflows/main.yml | 5 +++ CHANGES.md | 1 + README.md | 4 +++ pyproject.toml | 2 ++ rex9/core.py | 11 +++---- rex9/util/date.py | 28 ----------------- tests/test_util_date.py | 63 ++++++++++++++++++++++++++------------ 7 files changed, 60 insertions(+), 54 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2db79f7..298c267 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,11 @@ jobs: - name: Acquire sources uses: actions/checkout@v3 + - name: Generate locales + if: runner.os == 'Linux' + run: | + sudo apt-get update && sudo apt-get install --yes tzdata locales && sudo locale-gen de_DE.UTF-8 + - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/CHANGES.md b/CHANGES.md index 5b1a3fd..7554237 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,3 +10,4 @@ - Fix return journey wrt. arrival time - Process HAFAS remarks - Add colored terminal output +- Parse date-/time-range expressions using the Aika library diff --git a/README.md b/README.md index 32314c3..8055072 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ and further stops on the trip, and computes travel plan drafts using the [HAFAS] rex9 travel --from=Oranienburg --to=Stralsund --stops=Fürstenberg,Mildenberg --when=do-di ``` +The `--when` argument accepts all date-/time-range expressions as provided by the [Aika] +package, which currently supports English and German. + ## Glossary @@ -46,4 +49,5 @@ poe check ``` +[Aika]: https://github.com/panodata/aika [HAFAS]: https://de.wikipedia.org/wiki/HAFAS diff --git a/pyproject.toml b/pyproject.toml index 3271c5c..7ca35d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ classifiers = [ ] dependencies = [ + "aika==0.1", "click<=9", "colorlog<7", "markdown2<3", @@ -72,6 +73,7 @@ release = [ ] test = [ "coverage<8", + "freezegun<1.3", "pytest<8", ] [tool.setuptools] diff --git a/rex9/core.py b/rex9/core.py index fbc5093..d96edc1 100644 --- a/rex9/core.py +++ b/rex9/core.py @@ -6,13 +6,13 @@ import typing as t from textwrap import indent +from aika import DaterangeExpression from pyhafas import HafasClient from pyhafas.profile import DBProfile from pyhafas.types.fptf import Remark from rex9.model import TimeMode, TravelJourney, TravelJourneySegment, TravelPlan, TravelPlanSegment -from rex9.util.cli import split_list -from rex9.util.date import format_date_weekday, format_time, next_date_by_weekday +from rex9.util.date import format_date_weekday, format_time from rex9.util.format import CliFormatter as cf from rex9.util.pyhafas import query_for @@ -41,11 +41,8 @@ def make_travelplan(origin: str, destination: str, stops: t.List[str], when: str location_remote = client.locations(destination)[0] # Resolve departure and arrival times of home and remote locations. - on_begin, on_end = split_list(when, delimiter="-") - date_begin = next_date_by_weekday(on_begin) - date_end = next_date_by_weekday(on_end, start=date_begin) - datetime_begin = dt.datetime.combine(date_begin, DEFAULT_DEPARTURE_TIME) - datetime_end = dt.datetime.combine(date_end, DEFAULT_ARRIVAL_TIME) + dr = DaterangeExpression(default_start_time=DEFAULT_DEPARTURE_TIME, default_end_time=DEFAULT_ARRIVAL_TIME) + datetime_begin, datetime_end = dr.parse(when) # Report an intermediate summary. logger.info( diff --git a/rex9/util/date.py b/rex9/util/date.py index 89d0042..a672e02 100644 --- a/rex9/util/date.py +++ b/rex9/util/date.py @@ -4,34 +4,6 @@ import datetime as dt import typing as t -import dateutil.relativedelta as dtrel - - -class WeekdayMap: - days_en = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"] - days_de = ["MO", "DI", "MI", "DO", "FR", "SA", "SO"] - - def __init__(self): - self.weekdays = {} - for index, day in enumerate(self.days_en): - self.weekdays[day] = dtrel.weekday(index) - for index, day in enumerate(self.days_de): - self.weekdays[day] = dtrel.weekday(index) - - def find(self, label: str): - for key, value in self.weekdays.items(): - if key.lower().startswith(label.lower()): - return value - raise KeyError(f"Weekday not found: {label}") - - -def next_date_by_weekday(weekday: str, start=None): - start = start or dt.date.today() - weekday_map = WeekdayMap() - weekday_dtrel = weekday_map.find(weekday) - delta = dtrel.relativedelta(days=1, weekday=weekday_dtrel) - return start + delta - def format_date_weekday(datetime: dt.datetime): return f"{datetime} ({datetime.strftime('%A')})" diff --git a/tests/test_util_date.py b/tests/test_util_date.py index 39a66cd..9ab0e0c 100644 --- a/tests/test_util_date.py +++ b/tests/test_util_date.py @@ -3,22 +3,47 @@ import datetime as dt -import pytest - -from rex9.util.date import next_date_by_weekday - - -def test_weekdaymap_de(): - date_value = next_date_by_weekday("do") - assert isinstance(date_value, dt.date) - - -def test_weekdaymap_en(): - date_value = next_date_by_weekday("su") - assert isinstance(date_value, dt.date) - - -def test_weekdaymap_fail(): - with pytest.raises(KeyError) as ex: - next_date_by_weekday("foo") - assert ex.match("Weekday not found: foo") +from aika import DaterangeExpression +from freezegun import freeze_time + +TESTDRIVE_DATETIME = "2023-08-17T23:03:17+0200" + + +@freeze_time(TESTDRIVE_DATETIME) +def test_parse_daterange_relative_months(): + dr = DaterangeExpression( + default_start_time=dt.time(hour=9), + default_end_time=dt.time(hour=17), + ) + assert ( + dr.parse("next March") + == dr.parse("im März") + == ( + dt.datetime(2023, 3, 1, 9, 0, 0), + dt.datetime(2023, 3, 31, 17, 0, 0), + ) + ) + assert ( + dr.parse("July to December") + == dr.parse("Juli bis Dezember") + == ( + dt.datetime(2023, 7, 1, 9, 0, 0), + dt.datetime(2023, 12, 31, 17, 0, 0), + ) + ) + + +@freeze_time(TESTDRIVE_DATETIME) +def test_parse_daterange_relative_weekdays(): + dr = DaterangeExpression( + default_start_time=dt.time(hour=9), + default_end_time=dt.time(hour=17), + ) + assert ( + dr.parse("Sat - Tue") + == dr.parse("Sa-Di") + == ( + dt.datetime(2023, 8, 19, 9, 0, 0), + dt.datetime(2023, 8, 22, 17, 0, 0), + ) + )