Skip to content

Commit 176e920

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents a909c8d + 19fac2b commit 176e920

File tree

6 files changed

+96
-61
lines changed

6 files changed

+96
-61
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ repos:
2424
hooks:
2525
- id: unittest
2626
name: unittest
27-
entry: make test
27+
entry: make test doctest
2828
language: python
2929
'types': [python]
3030
pass_filenames: false

.pylintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[MESSAGES CONTROL]
2-
disable=missing-function-docstring, unspecified-encoding
2+
disable=missing-function-docstring

docs/things/database.html

+66-50
Large diffs are not rendered by default.

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def package_files(directory):
2525
"argv_emulation": False,
2626
}
2727

28-
with open("README.md", "r") as fh:
28+
with open("README.md", "r", encoding="utf-8") as fh:
2929
LONG_DESRIPTION = fh.read()
3030

3131
setup(
@@ -45,7 +45,7 @@ def package_files(directory):
4545
"Operating System :: MacOS :: MacOS X",
4646
"Natural Language :: English",
4747
],
48-
python_requires=">=3.6",
48+
python_requires=">=3.7",
4949
data_files=DATA_FILES,
5050
options={"py2app": OPTIONS},
5151
setup_requires=["py2app"],

tests/test_things.py

+4
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ def test_anytime(self):
165165
def test_logbook(self):
166166
tasks = things.logbook()
167167
self.assertEqual(21, len(tasks))
168+
tasks = things.logbook(stop_date="2099-03-29")
169+
self.assertEqual(0, len(tasks))
170+
tasks = things.logbook(stop_date="2021-03-28")
171+
self.assertEqual(21, len(tasks))
168172

169173
def test_canceled(self):
170174
tasks = things.canceled()

things/database.py

+22-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import re
66
import sqlite3
77
from textwrap import dedent
8+
import datetime
89

910

1011
# --------------------------------------------------
@@ -83,6 +84,7 @@
8384
DATE_DEADLINE = "dueDate"
8485
DATE_MODIFIED = "userModificationDate"
8586
DATE_START = "startDate"
87+
DATE_STOP = "stopDate"
8688

8789
# --------------------------------------------------
8890
# Various filters
@@ -161,7 +163,7 @@ def __init__(self, filepath=None, print_sql=False):
161163
# Automated migration to new database location in Things 3.12.6/3.13.1
162164
# --------------------------------
163165
try:
164-
with open(self.filepath) as file:
166+
with open(self.filepath, encoding="utf-8") as file:
165167
if "Your database file has been moved there" in file.readline():
166168
self.filepath = DEFAULT_FILEPATH
167169
except (UnicodeDecodeError, FileNotFoundError, PermissionError):
@@ -181,6 +183,7 @@ def get_tasks( # pylint: disable=R0914
181183
heading=None,
182184
tag=None,
183185
start_date=None,
186+
stop_date=None,
184187
deadline=None,
185188
deadline_suppressed=None,
186189
trashed=False,
@@ -246,6 +249,7 @@ def get_tasks( # pylint: disable=R0914
246249
{make_filter("TASK.dueDateSuppressionDate", deadline_suppressed)}
247250
{make_filter("TAG.title", tag)}
248251
{make_date_filter(f"TASK.{DATE_START}", start_date)}
252+
{make_date_filter(f"TASK.{DATE_STOP}", stop_date)}
249253
{make_date_filter(f"TASK.{DATE_DEADLINE}", deadline)}
250254
{make_date_range_filter(f"TASK.{DATE_CREATED}", last)}
251255
{make_search_filter(search_query)}
@@ -637,9 +641,10 @@ def make_date_filter(date_column: str, value) -> str:
637641
date_column : str
638642
Name of the column that has date information on a task.
639643
640-
value : bool, 'future', 'past', or None
644+
value : bool, 'future', 'past', ISO 8601 date str, or None
641645
`True` or `False` indicates whether a date is set or not.
642646
`'future'` or `'past'` indicates a date in the future or past.
647+
ISO 8601 date str is in the format "YYYY-MM-DD".
643648
`None` indicates any value.
644649
645650
Returns
@@ -659,6 +664,9 @@ def make_date_filter(date_column: str, value) -> str:
659664
>>> make_date_filter('startDate', 'future')
660665
"AND date(startDate, 'unixepoch') > date('now', 'localtime')"
661666
667+
>>> make_date_filter('stopDate', '2021-03-28')
668+
"AND date(stopDate, 'unixepoch') >= date('2021-03-28')"
669+
662670
>>> make_date_filter('created', None)
663671
''
664672
@@ -669,18 +677,25 @@ def make_date_filter(date_column: str, value) -> str:
669677
if isinstance(value, bool):
670678
return make_filter(date_column, value)
671679

672-
# compare `date_column` to now.
673-
validate("value", value, ["future", "past"])
680+
try:
681+
# Check for ISO 8601 date str
682+
datetime.date.fromisoformat(value)
683+
threshold = f"date('{value}')"
684+
comparator = '>='
685+
except ValueError:
686+
# "future" or "past"
687+
validate("value", value, ["future", "past"])
688+
threshold = "date('now', 'localtime')"
689+
comparator = ">" if value == "future" else "<="
690+
674691
# Note: not using "localtime" modifier on `date()` since Things.app
675692
# seems to store `startDate` and `dueDate` as a 00:00 UTC datetime.
676693
# Morever, note that `stopDate` -- contrary to its name -- seems to
677694
# be stored as the full UTC datetime of when the task was "stopped".
678695
# See also: https://github.com/thingsapi/things.py/issues/93
679696
date = f"date({date_column}, 'unixepoch')"
680-
operator = ">" if value == "future" else "<="
681-
now = "date('now', 'localtime')"
682697

683-
return f"AND {date} {operator} {now}"
698+
return f"AND {date} {comparator} {threshold}"
684699

685700

686701
def make_date_range_filter(date_column, offset) -> str:

0 commit comments

Comments
 (0)