Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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.
4 changes: 4 additions & 0 deletions tests/test_things.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,10 @@ def test_thingsdate(self):
self.assertEqual("AND stopDate IS NULL", sqlfilter)


def test_thingstime(self):
test_task = things.get("7F4vqUNiTvGKaCUfv5pqYG")['reminder_time']
self.assertEqual(test_task, "12:34")

if __name__ == "__main__":
unittest.main()
ThingsCase() # For Vulture
56 changes: 53 additions & 3 deletions things/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"heading_title",
"project",
"project_title",
"reminder_time",
"trashed",
"tags",
)
Expand Down Expand Up @@ -107,6 +108,8 @@
# See `convert_isodate_sql_expression_to_thingsdate` for details.
DATE_DEADLINE = "deadline" # INTEGER: YYYYYYYYYYYMMMMDDDDD0000000, in binary
DATE_START = "startDate" # INTEGER: YYYYYYYYYYYMMMMDDDDD0000000, in binary
# See 'convert_thingstime_sql_expression_to_isotime' for details.
REMINDER_TIME = "reminderTime" # INTEGER: hhhhhmmmmmm00000000000000000000, in binary

# --------------------------------------------------
# Various filters
Expand Down Expand Up @@ -528,6 +531,9 @@ def make_tasks_sql_query(where_predicate=None, order_predicate=None):
deadline_expression = convert_thingsdate_sql_expression_to_isodate(
f"TASK.{DATE_DEADLINE}"
)
reminder_time_expression = convert_thingstime_sql_expression_to_isotime(
f"TASK.{REMINDER_TIME}"
)

return f"""
SELECT DISTINCT
Expand Down Expand Up @@ -576,8 +582,9 @@ def make_tasks_sql_query(where_predicate=None, order_predicate=None):
CASE
WHEN CHECKLIST_ITEM.uuid IS NOT NULL THEN 1
END AS checklist,
date({start_date_expression}) AS start_date,
date({deadline_expression}) AS deadline,
{start_date_expression} AS start_date,
{deadline_expression} AS deadline,
{reminder_time_expression} AS "reminder_time",
datetime(TASK.{DATE_STOP}, "unixepoch", "localtime") AS "stop_date",
datetime(TASK.{DATE_CREATED}, "unixepoch", "localtime") AS created,
datetime(TASK.{DATE_MODIFIED}, "unixepoch", "localtime") AS modified,
Expand Down Expand Up @@ -703,6 +710,49 @@ def convert_thingsdate_sql_expression_to_isodate(sql_expression):
return f"CASE WHEN {thingsdate} THEN {isodate} ELSE {thingsdate} END"


def convert_thingstime_sql_expression_to_isotime(sql_expression: str) -> str:
"""
Return SQL Expression that decodes a Things time as a string.

A _Things time_ is an integer where the binary digits are
hhhhhmmmmmm00000000000000000000; h is hours, m is minutes.
Seconds are not encoded in a Things time.

For example, the ISO 8601 time '12:34:00' corresponds to the Things
time 840957952 as integer; in binary that is:
0110010001000000000000000000000
hhhhhmmmmmm00000000000000000000
12 34 00

Parameters
----------
sql_expression : str
A sql expression pointing to a "Things time" integer in
format hhhhhmmmmmm00000000000000000000, in binary.



Example
-------
>>> convert_thingstime_sql_expression_to_isotime(TASK.reminderTime)
"time(CASE WHEN TASK.reminderTime THEN \
printf('%02d:%02d', (TASK.reminderTime & 2080374784) >> 26, \
(TASK.reminderTime & 66060288) >> 20) \
ELSE TASK.reminderTime END) AS "reminder_time"",
"""

h_mask = 0b1111100000000000000000000000000
m_mask = 0b0000011111100000000000000000000

thingstime = sql_expression
hours = f"({thingstime} & {h_mask}) >> 26"
minutes = f"({thingstime} & {m_mask}) >> 20"

isotime = f"printf('%02d:%02d', {hours}, {minutes})"
# when thingstime is NULL, return thingstime as-is
return f"CASE WHEN {thingstime} THEN {isotime} ELSE {thingstime} END"


def dict_factory(cursor, row):
"""
Convert SQL result into a dictionary.
Expand Down Expand Up @@ -1192,4 +1242,4 @@ def validate_offset(parameter, argument):
f"Please specify a string of the format 'X[d/w/y]' "
"where X is a non-negative integer followed by 'd', 'w', or 'y' "
"that indicates days, weeks, or years."
)
)
Loading