diff --git a/setup.py b/setup.py index 612fdeb..84fa827 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ def package_files(directory): "argv_emulation": False, } -with open("README.md", "r") as fh: +with open("README.md", "r", encoding="utf-8") as fh: LONG_DESRIPTION = fh.read() setup( @@ -45,7 +45,7 @@ def package_files(directory): "Operating System :: MacOS :: MacOS X", "Natural Language :: English", ], - python_requires=">=3.6", + python_requires=">=3.7", data_files=DATA_FILES, options={"py2app": OPTIONS}, setup_requires=["py2app"], diff --git a/tests/test_things.py b/tests/test_things.py index 6a36c7e..1f0058f 100644 --- a/tests/test_things.py +++ b/tests/test_things.py @@ -165,6 +165,10 @@ def test_anytime(self): def test_logbook(self): tasks = things.logbook() self.assertEqual(21, len(tasks)) + tasks = things.logbook(stop_date="2099-03-29") + self.assertEqual(0, len(tasks)) + tasks = things.logbook(stop_date="2021-03-28") + self.assertEqual(21, len(tasks)) def test_canceled(self): tasks = things.canceled() diff --git a/things/__init__.py b/things/__init__.py index 27a526a..4272be2 100644 --- a/things/__init__.py +++ b/things/__init__.py @@ -4,7 +4,7 @@ __copyright__ = "2021 Alexander Willner & Michael Belfrage" __credits__ = ["Alexander Willner", "Michael Belfrage"] __license__ = "Apache License 2.0" -__version__ = "0.0.12" +__version__ = "0.0.13" __maintainer__ = ["Alexander Willner", "Michael Belfrage"] __email__ = "alex@willner.ws" __status__ = "Development" diff --git a/things/database.py b/things/database.py index c568571..931aa4e 100755 --- a/things/database.py +++ b/things/database.py @@ -5,6 +5,7 @@ import re import sqlite3 from textwrap import dedent +from datetime import datetime # -------------------------------------------------- @@ -83,6 +84,7 @@ DATE_DEADLINE = "dueDate" DATE_MODIFIED = "userModificationDate" DATE_START = "startDate" +DATE_STOP = "stopDate" # -------------------------------------------------- # Various filters @@ -161,7 +163,7 @@ def __init__(self, filepath=None, print_sql=False): # Automated migration to new database location in Things 3.12.6/3.13.1 # -------------------------------- try: - with open(self.filepath) as file: + with open(self.filepath, encoding="utf-8") as file: if "Your database file has been moved there" in file.readline(): self.filepath = DEFAULT_FILEPATH except (UnicodeDecodeError, FileNotFoundError, PermissionError): @@ -181,6 +183,7 @@ def get_tasks( # pylint: disable=R0914 heading=None, tag=None, start_date=None, + stop_date=None, deadline=None, deadline_suppressed=None, trashed=False, @@ -246,6 +249,7 @@ def get_tasks( # pylint: disable=R0914 {make_filter("TASK.dueDateSuppressionDate", deadline_suppressed)} {make_filter("TAG.title", tag)} {make_date_filter(f"TASK.{DATE_START}", start_date)} + {make_date_filter(f"TASK.{DATE_STOP}", stop_date)} {make_date_filter(f"TASK.{DATE_DEADLINE}", deadline)} {make_date_range_filter(f"TASK.{DATE_CREATED}", last)} {make_search_filter(search_query)} @@ -635,9 +639,10 @@ def make_date_filter(date_column: str, value) -> str: date_column : str Name of the column that has date information on a task. - value : bool, 'future', 'past', or None + value : bool, 'future', 'past', ISO 8601 date or None `True` or `False` indicates whether a date is set or not. `'future'` or `'past'` indicates a date in the future or past. + `ISO 8601` date is a string in the format `YYYY-MM-DD`. `None` indicates any value. Returns @@ -657,6 +662,9 @@ def make_date_filter(date_column: str, value) -> str: >>> make_date_filter('start_date', 'future') "AND date(start_date, 'unixepoch', 'localtime') > date('now', 'localtime')" + >>> make_date_filter('stop_date', '2021-03-28') + "AND date(stop_date, 'unixepoch', 'localtime') >= date('2021-03-28', 'localtime')" + >>> make_date_filter('created', None) '' @@ -668,10 +676,14 @@ def make_date_filter(date_column: str, value) -> str: return make_filter(date_column, value) # compare `date_column` to now. - validate("value", value, ["future", "past"]) + try: + now = f"date('{datetime.fromisoformat(value)}', 'localtime')" + operator = ">=" + except ValueError: + validate("value", value, ["future", "past"]) + operator = ">" if value == "future" else "<=" + now = "date('now', 'localtime')" date = f"date({date_column}, 'unixepoch', 'localtime')" - operator = ">" if value == "future" else "<=" - now = "date('now', 'localtime')" return f"AND {date} {operator} {now}"