Skip to content
This repository was archived by the owner on Oct 9, 2023. It is now read-only.

Commit e669cf5

Browse files
Made datetime attribute insert and match time-zone invariant (#301)
## What is the goal of this PR? Enables the BDD tests to check timezone-invariance of inserting and reading datetime attributes. ## What are the changes implemented in this PR? - During deserialization of datetime, we incorrectly used `fromtimestamp` function. This function is environment timezone-sensitive. The correct function to use is `utcfromtimestamp` which converts the milliseconds since epoch to UTC datetime object. This conforms to the serialization process: we convert datetime object to milliseconds since epoch assuming the datetime is in UTC. - Added step which sets environment timezone to `time_zone_label` - Fixed parsing of class `AttributeMatcher` and `ValueMatcher`. Fixes #298.
1 parent c6b6850 commit e669cf5

File tree

14 files changed

+32
-11
lines changed

14 files changed

+32
-11
lines changed

.factory/automation.yml

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ build:
113113
type: foreground
114114
command: |
115115
export PATH="$HOME/.local/bin:$PATH"
116+
sudo apt-get update
116117
sudo apt install python3-pip -y
117118
python3 -m pip install -U pip
118119
python3 -m pip install -r requirements_dev.txt

dependencies/vaticle/repositories.bzl

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ def vaticle_typedb_behaviour():
3939
git_repository(
4040
name = "vaticle_typedb_behaviour",
4141
remote = "https://github.com/vaticle/typedb-behaviour",
42-
commit = "767bf98fef7383addf42a1ae6e97a44874bb4f0b" # sync-marker: do not remove this comment, this is used for sync-dependencies by @vaticle_typedb_behaviour
42+
commit = "90b4addcd71a398e9e52e322021c49310e66a47a" # sync-marker: do not remove this comment, this is used for sync-dependencies by @vaticle_typedb_behaviour
4343
)
44+

tests/behaviour/concept/serialization/json/BUILD

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ typedb_behaviour_py_test(
4040
"//tests/behaviour/connection:steps",
4141
"//tests/behaviour/connection/database:steps",
4242
"//tests/behaviour/connection/session:steps",
43-
"//tests/behaviour/connection/transaction:steps",
43+
"//tests/behaviour/connection/transaction:steps"
4444
],
4545
deps = [
4646
"//:client_python",

tests/behaviour/concept/thing/attribute/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ typedb_behaviour_py_test(
4343
"//tests/behaviour/connection/database:steps",
4444
"//tests/behaviour/connection/session:steps",
4545
"//tests/behaviour/connection/transaction:steps",
46+
"//tests/behaviour/util:util_steps"
4647
],
4748
deps = [
4849
"//:client_python",

tests/behaviour/concept/thing/attribute/attribute_steps.py

+1
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,4 @@ def step_impl(context: Context, var: str, value: str):
149149
@step("attribute {var:Var} has datetime value: {value:DateTime}")
150150
def step_impl(context: Context, var: str, value: datetime):
151151
assert_that(context.get(var).as_attribute().get_value(), is_(value))
152+

tests/behaviour/config/parameters.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,18 @@ def parseWords(text):
5858

5959

6060
@parse.with_pattern(r"\d\d\d\d-\d\d-\d\d(?: \d\d:\d\d:\d\d)?")
61+
def parse_datetime_pattern(text: str) -> datetime:
62+
return parse_datetime(str)
63+
64+
6165
def parse_datetime(text: str) -> datetime:
6266
try:
63-
return datetime.strptime(text, "%Y-%m-%d %H:%M:%S")
67+
return datetime.strptime(text, "%Y-%m-%dT%H:%M:%S")
6468
except ValueError:
65-
return datetime.strptime(text, "%Y-%m-%d")
69+
try:
70+
return datetime.strptime(text, "%Y-%m-%d %H:%M:%S")
71+
except ValueError:
72+
return datetime.strptime(text, "%Y-%m-%d")
6673

6774

6875
register_type(DateTime=parse_datetime)

tests/behaviour/typeql/language/insert/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ typedb_behaviour_py_test(
3434
"//tests/behaviour/connection/session:steps",
3535
"//tests/behaviour/connection/transaction:steps",
3636
"//tests/behaviour/typeql:steps",
37+
"//tests/behaviour/util:util_steps"
3738
],
3839
deps = [
3940
"//:client_python",

tests/behaviour/typeql/typeql_steps.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ class AttributeMatcher(ConceptMatcher, ABC):
225225

226226
def __init__(self, type_and_value: str):
227227
self.type_and_value = type_and_value
228-
s = type_and_value.split(":")
228+
s = type_and_value.split(":", 1)
229229
assert_that(s, has_length(2),
230230
"[%s] is not a valid attribute identifier. It should have format \"type_label:value\"." % type_and_value)
231231
self.type_label, self.value_string = s
@@ -284,7 +284,7 @@ class ValueMatcher(ConceptMatcher):
284284

285285
def __init__(self, value_type_and_value: str):
286286
self.value_type_and_value = value_type_and_value
287-
s = value_type_and_value.split(":")
287+
s = value_type_and_value.split(":", 1)
288288
assert_that(s, has_length(2),
289289
"[%s] is not a valid identifier. It should have format \"value_type:value\"." % value_type_and_value)
290290
self.value_type_name, self.value_string = s

tests/behaviour/util/util_steps.py

+7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525

2626
from tests.behaviour.context import Context
2727

28+
import os
29+
import time
30+
31+
@step("set time-zone is: {time_zone_label}")
32+
def step_impl(context: Context, time_zone_label: str):
33+
os.environ["TZ"] = time_zone_label
34+
time.tzset()
2835

2936
@step("wait {seconds} seconds")
3037
def step_impl(context: Context, seconds: str):

tool/behave_rule.bzl

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def _rule_implementation(ctx):
118118
cmd += " && rm -rf " + steps_out_dir
119119
cmd += " && mkdir " + steps_out_dir + " && "
120120
cmd += " && ".join(["cp %s %s" % (step_file.path, steps_out_dir) for step_file in ctx.files.steps])
121-
cmd += " && behave %s --no-capture -D port=$PORT && export RESULT=0 || export RESULT=1" % feats_dir
121+
cmd += " && python3 -m behave %s --no-capture -D port=$PORT && export RESULT=0 || export RESULT=1" % feats_dir
122122
cmd += """
123123
echo Tests concluded with exit value $RESULT
124124
echo Stopping server.

typedb/common/exception.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def __init__(self, code: int, message: str):
116116
NONEXISTENT_EXPLAINABLE_CONCEPT = ConceptErrorMessage(10, "The concept identified by '%s' is not explainable.")
117117
NONEXISTENT_EXPLAINABLE_OWNERSHIP = ConceptErrorMessage(11, "The ownership by owner '%s' of attribute '%s' is not explainable.")
118118
GET_HAS_WITH_MULTIPLE_FILTERS = ConceptErrorMessage(12, "Only one filter can be applied at a time to get_has. The possible filters are: [attribute_type, attribute_types, annotations]")
119-
119+
UNSUPPORTED_TIMEZONE_INFORMATION = ConceptErrorMessage(13, "A date-time attribute cannot accept timezone aware datetime objects.")
120120

121121
class QueryErrorMessage(ErrorMessage):
122122

typedb/concept/proto/concept_proto_builder.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from typedb.api.concept.type.role_type import RoleType
2626
from typedb.api.concept.type.thing_type import ThingType, Annotations, Annotation
2727
from typedb.api.concept.type.type import Type
28-
from typedb.common.exception import TypeDBClientException, BAD_ENCODING, BAD_ANNOTATION
28+
from typedb.common.exception import TypeDBClientException, BAD_ENCODING, BAD_ANNOTATION, UNSUPPORTED_TIMEZONE_INFORMATION
2929
from typedb.common.rpc.request_builder import proto_role_type, proto_thing_type, byte_string
3030

3131

@@ -83,6 +83,8 @@ def string_value(value: str):
8383

8484

8585
def datetime_value(value: datetime):
86+
if value.tzinfo is not None:
87+
raise TypeDBClientException.of(UNSUPPORTED_TIMEZONE_INFORMATION)
8688
value_proto = concept_proto.ConceptValue()
8789
value_proto.date_time = int((value - datetime(1970, 1, 1)).total_seconds() * 1000)
8890
return value_proto

typedb/concept/thing/attribute.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def __init__(self, iid: str, is_inferred: bool, type_: DateTimeAttributeType, va
211211

212212
@staticmethod
213213
def of(thing_proto: concept_proto.Thing):
214-
return _DateTimeAttribute(concept_proto_reader.iid(thing_proto.iid), thing_proto.inferred, concept_proto_reader.attribute_type(thing_proto.type), datetime.fromtimestamp(float(thing_proto.value.date_time) / 1000.0))
214+
return _DateTimeAttribute(concept_proto_reader.iid(thing_proto.iid), thing_proto.inferred, concept_proto_reader.attribute_type(thing_proto.type), datetime.utcfromtimestamp(float(thing_proto.value.date_time) / 1000.0))
215215

216216
def get_type(self) -> "DateTimeAttributeType":
217217
return self._type

typedb/concept/value/value.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def __init__(self, value: datetime):
111111

112112
@staticmethod
113113
def of(value_proto: concept_proto.Value):
114-
return _DateTimeValue(datetime.fromtimestamp(float(value_proto.value.date_time) / 1000.0))
114+
return _DateTimeValue(datetime.utcfromtimestamp(float(value_proto.value.date_time) / 1000.0))
115115

116116
def get_value(self):
117117
return self._value

0 commit comments

Comments
 (0)