Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #117: add localtime to stop_date where clause #118

Merged
merged 13 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified tests/main.sqlite
Binary file not shown.
Binary file modified tests/main.sqlite-shm
Binary file not shown.
Binary file modified tests/main.sqlite-wal
Binary file not shown.
40 changes: 32 additions & 8 deletions tests/test_things.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import contextlib
import io
import os
import time
import sqlite3
import unittest
import unittest.mock
Expand Down Expand Up @@ -167,13 +168,13 @@ def test_anytime(self):

def test_logbook(self):
tasks = things.logbook()
self.assertEqual(21, len(tasks))
self.assertEqual(23, 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))
tasks = things.logbook(stop_date=">=2021-03-27")
self.assertEqual(21, len(tasks))
self.assertEqual(23, len(tasks))
tasks = things.logbook(stop_date="=2021-03-27")
self.assertEqual(0, len(tasks))

Expand All @@ -183,7 +184,7 @@ def test_canceled(self):

def test_completed(self):
tasks = things.completed()
self.assertEqual(10, len(tasks))
self.assertEqual(12, len(tasks))

def test_someday(self):
tasks = things.someday()
Expand All @@ -199,11 +200,11 @@ def test_get_by_uuid(self):

def test_todos(self):
todos = things.todos(start="Anytime", status="completed")
self.assertEqual(6, len(todos))
self.assertEqual(8, len(todos))
todos = things.todos(start="Anytime")
self.assertEqual(10, len(todos))
todos = things.todos(status="completed")
self.assertEqual(10, len(todos))
self.assertEqual(12, len(todos))
todos = things.todos(include_items=True)
self.assertEqual(15, len(todos))
tasks = things.tasks(include_items=True)
Expand Down Expand Up @@ -304,7 +305,7 @@ def test_last(self):
self.assertEqual(len(last_tasks), 19)

last_tasks = things.last("100y", status="completed")
self.assertEqual(len(last_tasks), 10)
self.assertEqual(len(last_tasks), 12)

with self.assertRaises(ValueError):
things.last(None)
Expand All @@ -323,7 +324,7 @@ def test_last(self):

def test_tasks(self):
count = things.tasks(status="completed", last="100y", count_only=True)
self.assertEqual(count, 10)
self.assertEqual(count, 12)

count = things.last("1y", tag="Important", status="completed", count_only=True)
self.assertEqual(count, 0)
Expand All @@ -348,6 +349,29 @@ def test_tasks(self):
with self.assertRaises((sqlite3.ProgrammingError, ValueError)):
things.tasks(area="\0")

def test_tasks_stopdate_timezones(self):
# see https://github.com/thingsapi/things.py/issues/117
# this test looks at changes to two tasks based on timezone, on either side of midnight UTC

# make sure we get back both tasks completed for date by midnight UTC+5
# change timezone to Pakistan
os.environ['TZ'] = 'UTC-5' # UTC+5, per https://unix.stackexchange.com/a/104091
time.tzset()
tasks = things.tasks(stop_date="2024-06-18", status="completed", count_only=True)
self.assertEqual(tasks, 2)

# make sure we get back one task completed for date by midnight UTC
os.environ['TZ'] = 'UTC'
time.tzset()
tasks = things.tasks(stop_date="2024-06-18", status="completed", count_only=True)
self.assertEqual(tasks, 1)

# change timezone to New York
os.environ['TZ'] = 'UTC+5' # UTC-5, per https://unix.stackexchange.com/a/104091
time.tzset()
tasks = things.tasks(stop_date="2024-06-18", status="completed", count_only=True)
self.assertEqual(tasks, 0)

def test_database_details(self):
output = io.StringIO()
with contextlib.redirect_stdout(output):
Expand Down Expand Up @@ -380,7 +404,7 @@ def test_thingsdate(self):
self.assertEqual("AND deadline == 132464128", sqlfilter)
sqlfilter = things.database.make_unixtime_filter("stopDate", "future")
self.assertEqual(
"AND date(stopDate, 'unixepoch') > date('now', 'localtime')", sqlfilter
"AND date(stopDate, 'unixepoch', 'localtime') > date('now', 'localtime')", sqlfilter
)
sqlfilter = things.database.make_unixtime_filter("stopDate", False)
self.assertEqual("AND stopDate IS NULL", sqlfilter)
Expand Down
4 changes: 2 additions & 2 deletions things/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def tasks(uuid=None, include_items=False, **kwargs): # noqa: C901
>>> things.tasks(area='hIo1FJlAYGKt1Yj38vzKc3', include_items=True)
[]
>>> things.tasks(status='completed', count_only=True)
10
12
>>> things.tasks(status='completed', last='1w', count_only=True)
0

Expand Down Expand Up @@ -605,7 +605,7 @@ def completed(**kwargs):
Examples
--------
>>> things.completed(count_only=True)
10
12
>>> things.completed(type='project', count_only=True)
0
>>> things.completed(type='to-do', last='1w')
Expand Down
14 changes: 7 additions & 7 deletions things/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,16 +962,16 @@ def make_unixtime_filter(date_column: str, value) -> str:
'AND stopDate IS NULL'

>>> make_unixtime_filter('stopDate', 'future')
"AND date(stopDate, 'unixepoch') > date('now', 'localtime')"
"AND date(stopDate, 'unixepoch', 'localtime') > date('now', 'localtime')"

>>> make_unixtime_filter('creationDate', '2021-03-28')
"AND date(creationDate, 'unixepoch') == date('2021-03-28')"
"AND date(creationDate, 'unixepoch', 'localtime') == date('2021-03-28')"

>>> make_unixtime_filter('creationDate', '=2021-03-28')
"AND date(creationDate, 'unixepoch') = date('2021-03-28')"
"AND date(creationDate, 'unixepoch', 'localtime') = date('2021-03-28')"

>>> make_unixtime_filter('creationDate', '<=2021-03-28')
"AND date(creationDate, 'unixepoch') <= date('2021-03-28')"
"AND date(creationDate, 'unixepoch', 'localtime') <= date('2021-03-28')"

>>> make_unixtime_filter('creationDate', None)
''
Expand All @@ -996,7 +996,7 @@ def make_unixtime_filter(date_column: str, value) -> str:
threshold = "date('now', 'localtime')"
comparator = ">" if value == "future" else "<="

date = f"date({date_column}, 'unixepoch')"
date = f"date({date_column}, 'unixepoch', 'localtime')"

return f"AND {date} {comparator} {threshold}"

Expand Down Expand Up @@ -1026,7 +1026,7 @@ def make_unixtime_range_filter(date_column: str, offset) -> str:
Examples
--------
>>> make_unixtime_range_filter('creationDate', '3d')
"AND datetime(creationDate, 'unixepoch') > datetime('now', '-3 days')"
"AND datetime(creationDate, 'unixepoch', 'localtime') > datetime('now', '-3 days')"

>>> make_unixtime_range_filter('creationDate', None)
''
Expand All @@ -1047,7 +1047,7 @@ def make_unixtime_range_filter(date_column: str, offset) -> str:
# Use `typing.assert_never(suffix)` from Python 3.11 onwards.
raise AssertionError("line should be unreachable.") # for static code analyzers

column_datetime = f"datetime({date_column}, 'unixepoch')"
column_datetime = f"datetime({date_column}, 'unixepoch', 'localtime')"
offset_datetime = f"datetime('now', '{modifier}')"

return f"AND {column_datetime} > {offset_datetime}"
Expand Down
Loading