Skip to content

Commit

Permalink
DateTime offset-aware data return
Browse files Browse the repository at this point in the history
  • Loading branch information
xzkostyan committed Sep 13, 2019
1 parent 08cc6de commit b412fd4
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 10 deletions.
12 changes: 9 additions & 3 deletions clickhouse_driver/columns/datetimecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ class DateTimeColumn(FormatColumn):
py_types = (datetime, int)
format = 'I'

def __init__(self, timezone=None, **kwargs):
def __init__(self, timezone=None, offset_naive=True, **kwargs):
self.timezone = timezone
self.offset_naive = offset_naive
super(DateTimeColumn, self).__init__(**kwargs)

def after_read_item(self, value):
return datetime.fromtimestamp(value, self.timezone)
dt = datetime.fromtimestamp(value, self.timezone)
return dt.replace(tzinfo=None) if self.offset_naive else dt

def before_write_item(self, value):
if isinstance(value, int):
Expand Down Expand Up @@ -46,15 +48,19 @@ def create_datetime_column(spec, column_options):
context = column_options['context']

tz_name = timezone = None
offset_naive = True

# Use column's timezone if it's specified.
if spec[-1] == ')':
tz_name = spec[10:-2]
offset_naive = False
else:
if not context.settings.get('use_client_time_zone', False):
tz_name = context.server_info.timezone

if tz_name:
timezone = get_timezone(tz_name)

return DateTimeColumn(timezone=timezone, **column_options)
return DateTimeColumn(
timezone=timezone, offset_naive=offset_naive, **column_options
)
140 changes: 133 additions & 7 deletions tests/columns/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,23 @@ def patch_env_tz(self, tz_name):
# Asia/Novosibirsk = UTC+7
# Europe/Moscow = UTC+3

# 1500000000 second since epoch in Europe/Moscow.
# 1500010800 second since epoch in UTC.
# 1500010800 second since epoch in Europe/Moscow.
# 1500000000 second since epoch in UTC.
dt = datetime(2017, 7, 14, 5, 40)
dt_tz = timezone('Asia/Kamchatka').localize(dt)

# INSERTs ans SELECTs must be the same as clickhouse-client's.
col_tz_name = 'Asia/Novosibirsk'
col_tz = timezone(col_tz_name)

# INSERTs and SELECTs must be the same as clickhouse-client's
# if column has no timezone.

def test_use_server_timezone(self):
"""
Insert datetime with timezone UTC
into column with no timezone
using server's timezone (Europe/Moscow)
"""
# Determine server timezone and calculate expected timestamp.
server_tz_name = self.client.execute('SELECT timezone()')[0][0]
offset = timezone(server_tz_name).utcoffset(self.dt).total_seconds()
Expand Down Expand Up @@ -120,6 +129,11 @@ def test_use_server_timezone(self):
self.assertEqual(inserted, [(self.dt, ), (self.dt, )])

def test_use_client_timezone(self):
"""
Insert datetime with timezone UTC
into column with no timezone
using client's timezone Asia/Novosibirsk
"""
settings = {'use_client_time_zone': True}

with self.patch_env_tz('Asia/Novosibirsk'):
Expand Down Expand Up @@ -176,6 +190,11 @@ def test_insert_integer_bounds(self):

@require_server_version(1, 1, 54337)
def test_datetime_with_timezone_use_server_timezone(self):
"""
Insert datetime with timezone Asia/Kamchatka
into column with no timezone
using server's timezone (Europe/Moscow)
"""
server_tz_name = self.client.execute('SELECT timezone()')[0][0]
offset = timezone(server_tz_name).utcoffset(self.dt)

Expand Down Expand Up @@ -206,6 +225,11 @@ def test_datetime_with_timezone_use_server_timezone(self):

@require_server_version(1, 1, 54337)
def test_datetime_with_timezone_use_client_timezone(self):
"""
Insert datetime with timezone Asia/Kamchatka
into column with no timezone
using client's timezone Asia/Novosibirsk
"""
settings = {'use_client_time_zone': True}

with self.patch_env_tz('Asia/Novosibirsk'):
Expand Down Expand Up @@ -241,8 +265,14 @@ def test_datetime_with_timezone_use_client_timezone(self):

@require_server_version(1, 1, 54337)
def test_column_use_server_timezone(self):
"""
Insert datetime with no timezone
into column with timezone Asia/Novosibirsk
using server's timezone (Europe/Moscow)
"""
with self.patch_env_tz('Europe/Moscow'):
with self.create_table("a DateTime('Asia/Novosibirsk')"):
table_col = "a DateTime('{}')".format(self.col_tz_name)
with self.create_table(table_col):
self.client.execute(
'INSERT INTO test (a) VALUES', [(self.dt, )]
)
Expand All @@ -264,14 +294,23 @@ def test_column_use_server_timezone(self):
)

inserted = self.client.execute(query)
self.assertEqual(inserted, [(self.dt, ), (self.dt, )])
self.assertEqual(inserted, [
(self.col_tz.localize(self.dt), ),
(self.col_tz.localize(self.dt), )
])

@require_server_version(1, 1, 54337)
def test_column_use_client_timezone(self):
"""
Insert datetime with no timezone
into column with timezone Asia/Novosibirsk
using client's timezone Europe/Moscow
"""
settings = {'use_client_time_zone': True}

with self.patch_env_tz('Europe/Moscow'):
with self.create_table("a DateTime('Asia/Novosibirsk')"):
table_col = "a DateTime('{}')".format(self.col_tz_name)
with self.create_table(table_col):
self.client.execute(
'INSERT INTO test (a) VALUES', [(self.dt, )],
settings=settings
Expand All @@ -294,4 +333,91 @@ def test_column_use_client_timezone(self):
)

inserted = self.client.execute(query, settings=settings)
self.assertEqual(inserted, [(self.dt, ), (self.dt, )])
self.assertEqual(inserted, [
(self.col_tz.localize(self.dt), ),
(self.col_tz.localize(self.dt), )
])

@require_server_version(1, 1, 54337)
def test_datetime_with_timezone_column_use_server_timezone(self):
"""
Insert datetime with timezone Asia/Kamchatka
into column with timezone Asia/Novosibirsk
using server's timezone (Europe/Moscow)
"""
with self.patch_env_tz('Europe/Moscow'):
table_col = "a DateTime('{}')".format(self.col_tz_name)
with self.create_table(table_col):
self.client.execute(
'INSERT INTO test (a) VALUES', [(self.dt_tz, )]
)

self.emit_cli(
"INSERT INTO test (a) VALUES "
"(toDateTime('2017-07-14 05:40:00', 'Asia/Kamchatka'))",
)

query = 'SELECT toInt32(a) FROM test'
inserted = self.emit_cli(query)
# 1499967600 = 1500000000 - 12 * 3600
self.assertEqual(inserted, '1499967600\n1499967600\n')

query = 'SELECT * FROM test'
inserted = self.emit_cli(query)
# 2017-07-14 00:40:00 = 2017-07-14 05:40:00 - 05:00:00
# (Kamchatka - Novosibirsk)
self.assertEqual(
inserted,
'2017-07-14 00:40:00\n2017-07-14 00:40:00\n'
)

inserted = self.client.execute(query)
dt = datetime(2017, 7, 14, 0, 40)
self.assertEqual(inserted, [
(self.col_tz.localize(dt), ),
(self.col_tz.localize(dt), )
])

@require_server_version(1, 1, 54337)
def test_datetime_with_timezone_column_use_client_timezone(self):
"""
Insert datetime with timezone Asia/Kamchatka
into column with timezone Asia/Novosibirsk
using client's timezone (Europe/Moscow)
"""
settings = {'use_client_time_zone': True}

with self.patch_env_tz('Europe/Moscow'):
table_col = "a DateTime('{}')".format(self.col_tz_name)
with self.create_table(table_col):
self.client.execute(
'INSERT INTO test (a) VALUES', [(self.dt_tz, )],
settings=settings
)

self.emit_cli(
"INSERT INTO test (a) VALUES "
"(toDateTime('2017-07-14 05:40:00', 'Asia/Kamchatka'))",
use_client_time_zone=1
)

query = 'SELECT toInt32(a) FROM test'
inserted = self.emit_cli(query, use_client_time_zone=1)
# 1499967600 = 1500000000 - 12 * 3600
self.assertEqual(inserted, '1499967600\n1499967600\n')

query = 'SELECT * FROM test'
inserted = self.emit_cli(query, use_client_time_zone=1)
# 2017-07-14 00:40:00 = 2017-07-14 05:40:00 - 05:00:00
# (Kamchatka - Novosibirsk)
self.assertEqual(
inserted,
'2017-07-14 00:40:00\n2017-07-14 00:40:00\n'
)

inserted = self.client.execute(query, settings=settings)
dt = datetime(2017, 7, 14, 0, 40)
self.assertEqual(inserted, [
(self.col_tz.localize(dt), ),
(self.col_tz.localize(dt), )
])

0 comments on commit b412fd4

Please sign in to comment.