55import re
66import sqlite3
77from textwrap import dedent
8+ from datetime import datetime
89
910
1011# --------------------------------------------------
8384DATE_DEADLINE = "dueDate"
8485DATE_MODIFIED = "userModificationDate"
8586DATE_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 )}
@@ -635,9 +639,10 @@ def make_date_filter(date_column: str, value) -> str:
635639 date_column : str
636640 Name of the column that has date information on a task.
637641
638- value : bool, 'future', 'past', or None
642+ value : bool, 'future', 'past', ISO 8601 date or None
639643 `True` or `False` indicates whether a date is set or not.
640644 `'future'` or `'past'` indicates a date in the future or past.
645+ `ISO 8601` date is a string in the format `YYYY-MM-DD`.
641646 `None` indicates any value.
642647
643648 Returns
@@ -657,6 +662,9 @@ def make_date_filter(date_column: str, value) -> str:
657662 >>> make_date_filter('start_date', 'future')
658663 "AND date(start_date, 'unixepoch', 'localtime') > date('now', 'localtime')"
659664
665+ >>> make_date_filter('stop_date', '2021-03-28')
666+ "AND date(stop_date, 'unixepoch', 'localtime') >= date('2021-03-28', 'localtime')"
667+
660668 >>> make_date_filter('created', None)
661669 ''
662670
@@ -668,10 +676,14 @@ def make_date_filter(date_column: str, value) -> str:
668676 return make_filter (date_column , value )
669677
670678 # compare `date_column` to now.
671- validate ("value" , value , ["future" , "past" ])
679+ try :
680+ now = f"date('{ datetime .fromisoformat (value )} ', 'localtime')"
681+ operator = ">="
682+ except ValueError :
683+ validate ("value" , value , ["future" , "past" ])
684+ operator = ">" if value == "future" else "<="
685+ now = "date('now', 'localtime')"
672686 date = f"date({ date_column } , 'unixepoch', 'localtime')"
673- operator = ">" if value == "future" else "<="
674- now = "date('now', 'localtime')"
675687
676688 return f"AND { date } { operator } { now } "
677689
0 commit comments