Skip to content

Commit 6d4413f

Browse files
amotlmatriv
andcommitted
Types: Unlock supporting timezone-aware DateTime fields
Co-authored-by: Marios Trivyzas <5058131+matriv@users.noreply.github.com>
1 parent df7fab9 commit 6d4413f

File tree

3 files changed

+79
-9
lines changed

3 files changed

+79
-9
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
CrateDB dialect table options.
1414
- Fixed SQL rendering of special DDL table options in `CrateDDLCompiler`.
1515
Before, configuring `crate_"translog.durability"` was not possible.
16+
- Unlocked supporting timezone-aware `DateTime` fields
1617

1718
## 2024/06/13 0.37.0
1819
- Added support for CrateDB's [FLOAT_VECTOR] data type and its accompanying

src/sqlalchemy_cratedb/dialect.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
CrateDDLCompiler,
3333
CrateIdentifierPreparer,
3434
)
35-
from crate.client.exceptions import TimezoneUnawareException
3635
from .sa_version import SA_VERSION, SA_1_4, SA_2_0
3736
from .type import FloatVector, ObjectArray, ObjectType
3837

@@ -114,14 +113,10 @@ def process(value):
114113

115114
class DateTime(sqltypes.DateTime):
116115

117-
TZ_ERROR_MSG = "Timezone aware datetime objects are not supported"
118-
119116
def bind_processor(self, dialect):
120117
def process(value):
121118
if value is not None:
122119
assert isinstance(value, datetime)
123-
if value.tzinfo is not None:
124-
raise TimezoneUnawareException(DateTime.TZ_ERROR_MSG)
125120
return value.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
126121
return value
127122
return process

tests/datetime_test.py

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
from __future__ import absolute_import
2323

2424
from datetime import datetime, tzinfo, timedelta
25+
import datetime as dt
2526
from unittest import TestCase, skipIf
2627
from unittest.mock import patch, MagicMock
2728

29+
import pytest
2830
import sqlalchemy as sa
29-
from sqlalchemy.exc import DBAPIError
30-
from sqlalchemy.orm import Session
31+
from sqlalchemy.orm import Session, sessionmaker
3132

3233
from sqlalchemy_cratedb import SA_VERSION, SA_1_4
3334

@@ -87,9 +88,82 @@ def test_date_can_handle_datetime(self):
8788
]
8889
self.session.query(self.Character).first()
8990

90-
def test_date_cannot_handle_tz_aware_datetime(self):
91+
def test_date_can_handle_tz_aware_datetime(self):
9192
character = self.Character()
9293
character.name = "Athur"
9394
character.timestamp = datetime(2009, 5, 13, 19, 19, 30, tzinfo=CST())
9495
self.session.add(character)
95-
self.assertRaises(DBAPIError, self.session.commit)
96+
97+
98+
Base = declarative_base()
99+
100+
101+
class FooBar(Base):
102+
__tablename__ = "foobar"
103+
name = sa.Column(sa.String, primary_key=True)
104+
date = sa.Column(sa.Date)
105+
datetime = sa.Column(sa.DateTime)
106+
107+
108+
@pytest.fixture
109+
def session(cratedb_service):
110+
engine = cratedb_service.database.engine
111+
session = sessionmaker(bind=engine)()
112+
113+
Base.metadata.drop_all(engine, checkfirst=True)
114+
Base.metadata.create_all(engine, checkfirst=True)
115+
return session
116+
117+
118+
@pytest.mark.skipif(SA_VERSION < SA_1_4, reason="Test case not supported on SQLAlchemy 1.3")
119+
def test_datetime_notz(session):
120+
"""
121+
An integration test for `sa.Date` and `sa.DateTime`, not using timezones.
122+
"""
123+
124+
# Insert record.
125+
foo_item = FooBar(
126+
name="foo",
127+
date=dt.date(2009, 5, 13),
128+
datetime=dt.datetime(2009, 5, 13, 19, 19, 30, 123456),
129+
)
130+
session.add(foo_item)
131+
session.commit()
132+
session.execute(sa.text("REFRESH TABLE foobar"))
133+
134+
# Query record.
135+
result = session.execute(sa.select(FooBar.name, FooBar.date, FooBar.datetime)).mappings().first()
136+
137+
# Compare outcome.
138+
assert result["date"].year == 2009
139+
assert result["datetime"].year == 2009
140+
assert result["datetime"].tzname() is None
141+
assert result["datetime"].timetz() == dt.time(19, 19, 30, 123000)
142+
assert result["datetime"].tzinfo is None
143+
144+
145+
@pytest.mark.skipif(SA_VERSION < SA_1_4, reason="Test case not supported on SQLAlchemy 1.3")
146+
def test_datetime_tz(session):
147+
"""
148+
An integration test for `sa.Date` and `sa.DateTime`, now using timezones.
149+
"""
150+
151+
# Insert record.
152+
foo_item = FooBar(
153+
name="foo",
154+
date=dt.date(2009, 5, 13),
155+
datetime=dt.datetime(2009, 5, 13, 19, 19, 30, 123456, tzinfo=CST()),
156+
)
157+
session.add(foo_item)
158+
session.commit()
159+
session.execute(sa.text("REFRESH TABLE foobar"))
160+
161+
# Query record.
162+
result = session.execute(sa.select(FooBar.name, FooBar.date, FooBar.datetime)).mappings().first()
163+
164+
# Compare outcome.
165+
assert result["date"].year == 2009
166+
assert result["datetime"].year == 2009
167+
assert result["datetime"].tzname() is None
168+
assert result["datetime"].timetz() == dt.time(19, 19, 30, 123000)
169+
assert result["datetime"].tzinfo is None

0 commit comments

Comments
 (0)