Skip to content

Commit c15c1db

Browse files
committed
Add timedelta option, fix timezone issues.
Naive datetime objects are now universally interpreted as localtime on the python client side. We had to do this because we need to figure out UTC time for uuids on the python side when generating from a datetime. Its awkward to have some things interpreted as local time on the python side while others as localtime on the db side. So all interpretation is done in python now. We recommend using aware datetime objects in any case, such objects have no ambiguity.
1 parent 20a8ddc commit c15c1db

File tree

3 files changed

+86
-4
lines changed

3 files changed

+86
-4
lines changed

nbs/00_vector.ipynb

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"import random\n",
6868
"from datetime import timedelta\n",
6969
"from datetime import datetime\n",
70+
"from datetime import timezone\n",
7071
"import calendar"
7172
]
7273
},
@@ -102,6 +103,10 @@
102103
" if time_arg is None:\n",
103104
" return uuid.uuid1(node, clock_seq)\n",
104105
" if hasattr(time_arg, 'utctimetuple'):\n",
106+
" # this is different from the Cassandra version, we assume that a naive datetime is in system time and convert it to UTC\n",
107+
" # we do this because naive datetimes are interpreted as timestamps (without timezone) in postgres\n",
108+
" if time_arg.tzinfo is None:\n",
109+
" time_arg = time_arg.astimezone(timezone.utc)\n",
105110
" seconds = int(calendar.timegm(time_arg.utctimetuple()))\n",
106111
" microseconds = (seconds * 1e6) + time_arg.time().microsecond\n",
107112
" else:\n",
@@ -291,21 +296,43 @@
291296
"source": [
292297
"#| export\n",
293298
"class UUIDTimeRange:\n",
294-
" def __init__(self, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, start_inclusive=True, end_inclusive=False):\n",
299+
" def __init__(self, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, time_delta: Optional[timedelta] = None, start_inclusive=True, end_inclusive=False):\n",
295300
" \"\"\"\n",
296-
" A UUIDTimeRange is a time range predicate on the UUID Version 1 timestamps.\n",
301+
" A UUIDTimeRange is a time range predicate on the UUID Version 1 timestamps. \n",
302+
" \n",
303+
" Note that naive datetime objects are interpreted as local time on the python client side and converted to UTC before being sent to the database.\n",
297304
" \"\"\"\n",
298305
" if start_date is not None and end_date is not None:\n",
299306
" if start_date > end_date:\n",
300307
" raise Exception(\"start_date must be before end_date\")\n",
301308
" \n",
302309
" if start_date is None and end_date is None:\n",
303310
" raise Exception(\"start_date and end_date cannot both be None\")\n",
311+
" \n",
312+
" if start_date is not None and start_date.tzinfo is None:\n",
313+
" start_date = start_date.astimezone(timezone.utc)\n",
314+
"\n",
315+
" if end_date is not None and end_date.tzinfo is None:\n",
316+
" end_date = end_date.astimezone(timezone.utc)\n",
317+
" \n",
318+
" if time_delta is not None:\n",
319+
" if end_date is None:\n",
320+
" end_date = start_date + time_delta\n",
321+
" elif start_date is None:\n",
322+
" start_date = end_date - time_delta\n",
323+
" else:\n",
324+
" raise Exception(\"time_delta, start_date and end_date cannot all be specified at the same time\")\n",
304325
"\n",
305326
" self.start_date = start_date\n",
306327
" self.end_date = end_date\n",
307328
" self.start_inclusive = start_inclusive\n",
308329
" self.end_inclusive = end_inclusive\n",
330+
" \n",
331+
" def __str__(self):\n",
332+
" start_str = f\"[{self.start_date}\" if self.start_inclusive else f\"({self.start_date}\"\n",
333+
" end_str = f\"{self.end_date}]\" if self.end_inclusive else f\"{self.end_date})\"\n",
334+
" \n",
335+
" return f\"UUIDTimeRange {start_str}, {end_str}\"\n",
309336
"\n",
310337
" def build_query(self, params: List) -> Tuple[str, List]:\n",
311338
" column = \"uuid_timestamp(id)\"\n",
@@ -1135,6 +1162,18 @@
11351162
"execution_count": null,
11361163
"metadata": {},
11371164
"outputs": [
1165+
{
1166+
"name": "stderr",
1167+
"output_type": "stream",
1168+
"text": [
1169+
"/Users/cevian/.pyenv/versions/3.11.4/envs/nbdev_env/lib/python3.11/site-packages/fastcore/docscrape.py:225: UserWarning: potentially wrong underline length... \n",
1170+
"Returns \n",
1171+
"-------- in \n",
1172+
"Retrieves similar records using a similarity query.\n",
1173+
"...\n",
1174+
" else: warn(msg)\n"
1175+
]
1176+
},
11381177
{
11391178
"data": {
11401179
"text/markdown": [
@@ -1409,6 +1448,13 @@
14091448
"assert len(rec) == 0\n",
14101449
"rec = await vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(specific_datetime-timedelta(days=7)))\n",
14111450
"assert len(rec) == 2\n",
1451+
"rec = await vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(start_date=specific_datetime, time_delta=timedelta(days=7)))\n",
1452+
"assert len(rec) == 1\n",
1453+
"#end is exclusive\n",
1454+
"rec = await vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(end_date=specific_datetime, time_delta=timedelta(days=7)))\n",
1455+
"assert len(rec) == 0\n",
1456+
"rec = await vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(end_date=specific_datetime+timedelta(seconds=1), time_delta=timedelta(days=7)))\n",
1457+
"assert len(rec) == 1\n",
14121458
"await vec.drop_table()\n",
14131459
"await vec.close()"
14141460
]
@@ -2105,6 +2151,13 @@
21052151
"assert len(rec) == 0\n",
21062152
"rec = vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(specific_datetime-timedelta(days=7)))\n",
21072153
"assert len(rec) == 2\n",
2154+
"rec = vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(start_date=specific_datetime, time_delta=timedelta(days=7)))\n",
2155+
"assert len(rec) == 1\n",
2156+
"#end is exclusive\n",
2157+
"rec = vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(end_date=specific_datetime, time_delta=timedelta(days=7)))\n",
2158+
"assert len(rec) == 0\n",
2159+
"rec = vec.search([1.0, 2.0], limit=4, uuid_time_filter=UUIDTimeRange(end_date=specific_datetime+timedelta(seconds=1), time_delta=timedelta(days=7)))\n",
2160+
"assert len(rec) == 1\n",
21082161
"vec.drop_table()\n",
21092162
"vec.close()"
21102163
]

timescale_vector/_modidx.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@
149149
'timescale_vector/client.py'),
150150
'timescale_vector.client.UUIDTimeRange.__init__': ( 'vector.html#uuidtimerange.__init__',
151151
'timescale_vector/client.py'),
152+
'timescale_vector.client.UUIDTimeRange.__str__': ( 'vector.html#uuidtimerange.__str__',
153+
'timescale_vector/client.py'),
152154
'timescale_vector.client.UUIDTimeRange.build_query': ( 'vector.html#uuidtimerange.build_query',
153155
'timescale_vector/client.py'),
154156
'timescale_vector.client.uuid_from_time': ( 'vector.html#uuid_from_time',

timescale_vector/client.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import random
1717
from datetime import timedelta
1818
from datetime import datetime
19+
from datetime import timezone
1920
import calendar
2021

2122
# %% ../nbs/00_vector.ipynb 6
@@ -44,6 +45,10 @@ def uuid_from_time(time_arg = None, node=None, clock_seq=None):
4445
if time_arg is None:
4546
return uuid.uuid1(node, clock_seq)
4647
if hasattr(time_arg, 'utctimetuple'):
48+
# this is different from the Cassandra version, we assume that a naive datetime is in system time and convert it to UTC
49+
# we do this because naive datetimes are interpreted as timestamps (without timezone) in postgres
50+
if time_arg.tzinfo is None:
51+
time_arg = time_arg.astimezone(timezone.utc)
4752
seconds = int(calendar.timegm(time_arg.utctimetuple()))
4853
microseconds = (seconds * 1e6) + time_arg.time().microsecond
4954
else:
@@ -195,21 +200,43 @@ def create_index_query(self, table_name_quoted:str, column_name_quoted: str, ind
195200

196201
# %% ../nbs/00_vector.ipynb 11
197202
class UUIDTimeRange:
198-
def __init__(self, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, start_inclusive=True, end_inclusive=False):
203+
def __init__(self, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, time_delta: Optional[timedelta] = None, start_inclusive=True, end_inclusive=False):
199204
"""
200-
A UUIDTimeRange is a time range predicate on the UUID Version 1 timestamps.
205+
A UUIDTimeRange is a time range predicate on the UUID Version 1 timestamps.
206+
207+
Note that naive datetime objects are interpreted as local time on the python client side and converted to UTC before being sent to the database.
201208
"""
202209
if start_date is not None and end_date is not None:
203210
if start_date > end_date:
204211
raise Exception("start_date must be before end_date")
205212

206213
if start_date is None and end_date is None:
207214
raise Exception("start_date and end_date cannot both be None")
215+
216+
if start_date is not None and start_date.tzinfo is None:
217+
start_date = start_date.astimezone(timezone.utc)
218+
219+
if end_date is not None and end_date.tzinfo is None:
220+
end_date = end_date.astimezone(timezone.utc)
221+
222+
if time_delta is not None:
223+
if end_date is None:
224+
end_date = start_date + time_delta
225+
elif start_date is None:
226+
start_date = end_date - time_delta
227+
else:
228+
raise Exception("time_delta, start_date and end_date cannot all be specified at the same time")
208229

209230
self.start_date = start_date
210231
self.end_date = end_date
211232
self.start_inclusive = start_inclusive
212233
self.end_inclusive = end_inclusive
234+
235+
def __str__(self):
236+
start_str = f"[{self.start_date}" if self.start_inclusive else f"({self.start_date}"
237+
end_str = f"{self.end_date}]" if self.end_inclusive else f"{self.end_date})"
238+
239+
return f"UUIDTimeRange {start_str}, {end_str}"
213240

214241
def build_query(self, params: List) -> Tuple[str, List]:
215242
column = "uuid_timestamp(id)"

0 commit comments

Comments
 (0)