Skip to content

Commit 163071d

Browse files
sfc-gh-stakedaankit-bhatnagar167
authored andcommitted
SNOW-57024: fixed negativ epoch timestamp converter for SnowSQL
1 parent d2ee8f4 commit 163071d

File tree

6 files changed

+131
-69
lines changed

6 files changed

+131
-69
lines changed

converter.py

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,56 @@ def _extract_timestamp(value, ctx):
109109
scale = ctx['scale']
110110
microseconds = float(
111111
value[0:-scale + 6]) if scale > 6 else float(value)
112-
fraction_of_nanoseconds = SnowflakeConverter._adjust_fraction_of_nanoseconds(
112+
fraction_of_nanoseconds = _adjust_fraction_of_nanoseconds(
113113
value, ctx['max_fraction'], scale)
114114

115115
return microseconds, fraction_of_nanoseconds
116116

117117

118+
def _adjust_fraction_of_nanoseconds(value, max_fraction, scale):
119+
if scale == 0:
120+
return 0
121+
if value[0] != '-':
122+
return int((value[-scale:] + ZERO_FILL[:9 - scale]))
123+
124+
frac = int(value[-scale:])
125+
if frac == 0:
126+
return 0
127+
else:
128+
return int(TO_UNICODE(max_fraction - frac) + ZERO_FILL[:9 - scale])
129+
130+
131+
def _generate_tzinfo_from_tzoffset(tzoffset_minutes):
132+
"""
133+
Generates tzinfo object from tzoffset.
134+
"""
135+
try:
136+
return _TZINFO_CLASS_CACHE[tzoffset_minutes]
137+
except KeyError:
138+
pass
139+
sign = u'P' if tzoffset_minutes >= 0 else u'N'
140+
abs_tzoffset_minutes = abs(tzoffset_minutes)
141+
hour, minute = divmod(abs_tzoffset_minutes, 60)
142+
name = u'GMT{sign:s}{hour:02d}{minute:02d}'.format(
143+
sign=sign,
144+
hour=hour,
145+
minute=minute)
146+
tzinfo_class_type = type(
147+
str(name), # str() for both Python 2 and 3
148+
(tzinfo,),
149+
dict(
150+
utcoffset=lambda self0, dt, is_dst=False: timedelta(
151+
minutes=tzoffset_minutes),
152+
tzname=lambda self0, dt: name,
153+
dst=lambda self0, dt: ZERO_TIMEDELTA,
154+
__repr__=lambda _: name
155+
)
156+
)
157+
tzinfo_cls = tzinfo_class_type()
158+
_TZINFO_CLASS_CACHE[tzoffset_minutes] = tzinfo_cls
159+
return tzinfo_cls
160+
161+
118162
class SnowflakeConverter(object):
119163
def __init__(self, **kwargs):
120164
self._parameters = {}
@@ -136,47 +180,6 @@ def get_parameters(self):
136180
def get_parameter(self, param):
137181
return self._parameters[param] if param in self._parameters else None
138182

139-
@staticmethod
140-
def _adjust_fraction_of_nanoseconds(value, max_fraction, scale):
141-
if scale == 0:
142-
return 0
143-
if value[0] != '-':
144-
return int((value[-scale:] + ZERO_FILL)[:9])
145-
146-
return int((TO_UNICODE(max_fraction - int(value[-scale:])) +
147-
ZERO_FILL)[:9])
148-
149-
@staticmethod
150-
def _generate_tzinfo_from_tzoffset(tzoffset_minutes):
151-
"""
152-
Generates tzinfo object from tzoffset.
153-
"""
154-
try:
155-
return _TZINFO_CLASS_CACHE[tzoffset_minutes]
156-
except KeyError:
157-
pass
158-
sign = u'P' if tzoffset_minutes >= 0 else u'N'
159-
abs_tzoffset_minutes = abs(tzoffset_minutes)
160-
hour, minute = divmod(abs_tzoffset_minutes, 60)
161-
name = u'GMT{sign:s}{hour:02d}{minute:02d}'.format(
162-
sign=sign,
163-
hour=hour,
164-
minute=minute)
165-
tzinfo_class_type = type(
166-
str(name), # str() for both Python 2 and 3
167-
(tzinfo,),
168-
dict(
169-
utcoffset=lambda self0, dt, is_dst=False: timedelta(
170-
minutes=tzoffset_minutes),
171-
tzname=lambda self0, dt: name,
172-
dst=lambda self0, dt: ZERO_TIMEDELTA,
173-
__repr__=lambda _: name
174-
)
175-
)
176-
tzinfo_cls = tzinfo_class_type()
177-
_TZINFO_CLASS_CACHE[tzoffset_minutes] = tzinfo_cls
178-
return tzinfo_cls
179-
180183
#
181184
# FROM Snowflake to Python Objects
182185
#
@@ -263,15 +266,13 @@ def _TIMESTAMP_TZ_to_python(self, ctx):
263266

264267
def conv0(encoded_value):
265268
value, tz = encoded_value.split()
266-
tzinfo = SnowflakeConverter._generate_tzinfo_from_tzoffset(
267-
int(tz) - 1440)
269+
tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
268270
return datetime.fromtimestamp(float(value), tz=tzinfo)
269271

270272
def conv(encoded_value):
271273
value, tz = encoded_value.split()
272274
microseconds = float(value[0:-scale + 6])
273-
tzinfo = SnowflakeConverter._generate_tzinfo_from_tzoffset(
274-
int(tz) - 1440)
275+
tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
275276
return datetime.fromtimestamp(microseconds, tz=tzinfo)
276277

277278
return conv if scale > 6 else conv0
@@ -592,8 +593,7 @@ def _time_to_snowflake(self, value):
592593
return value.strftime(u'%H:%M:%S')
593594

594595
def _struct_time_to_snowflake(self, value):
595-
tzinfo_value = SnowflakeConverter._generate_tzinfo_from_tzoffset(
596-
-time.timezone // 60)
596+
tzinfo_value = _generate_tzinfo_from_tzoffset(time.timezone // 60)
597597
t = datetime.fromtimestamp(time.mktime(value))
598598
if pytz.utc != tzinfo_value:
599599
t += tzinfo_value.utcoffset(t)

converter_issue23517.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
import pytz
1010

11-
from .converter import (SnowflakeConverter, ZERO_EPOCH)
11+
from .converter import (
12+
SnowflakeConverter,
13+
ZERO_EPOCH,
14+
_generate_tzinfo_from_tzoffset)
1215

1316
logger = getLogger(__name__)
1417

@@ -34,8 +37,7 @@ def _TIMESTAMP_TZ_to_python(self, ctx):
3437

3538
def conv0(encoded_value):
3639
value, tz = encoded_value.split()
37-
tzinfo = SnowflakeConverter._generate_tzinfo_from_tzoffset(
38-
int(tz) - 1440)
40+
tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
3941
microseconds = float(value)
4042
t = ZERO_EPOCH + timedelta(seconds=microseconds)
4143
if pytz.utc != tzinfo:
@@ -44,8 +46,7 @@ def conv0(encoded_value):
4446

4547
def conv(encoded_value):
4648
value, tz = encoded_value.split()
47-
tzinfo = SnowflakeConverter._generate_tzinfo_from_tzoffset(
48-
int(tz) - 1440)
49+
tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
4950
microseconds = float(value[0:-scale + 6])
5051
t = ZERO_EPOCH + timedelta(seconds=microseconds)
5152
if pytz.utc != tzinfo:
@@ -91,7 +92,7 @@ def _TIME_to_python(self, ctx):
9192
scale = ctx['scale']
9293

9394
conv0 = lambda value: (
94-
ZERO_EPOCH + timedelta(seconds=(float(value)))).time()
95+
ZERO_EPOCH + timedelta(seconds=(float(value)))).time()
9596

9697
def conv(value):
9798
microseconds = float(value[0:-scale + 6])

converter_snowsql.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212

1313
from .compat import TO_UNICODE, IS_WINDOWS
1414
from .constants import (is_timestamp_type_name, is_date_type_name)
15-
from .converter import (SnowflakeConverter, ZERO_EPOCH, _extract_timestamp)
15+
from .converter import (
16+
SnowflakeConverter,
17+
ZERO_EPOCH,
18+
_extract_timestamp,
19+
_adjust_fraction_of_nanoseconds,
20+
_generate_tzinfo_from_tzoffset)
1621
from .sfbinaryformat import (binary_to_python, SnowflakeBinaryFormat)
1722
from .sfdatetime import (
1823
SnowflakeDateTimeFormat,
@@ -149,8 +154,7 @@ def _TIMESTAMP_TZ_to_python(self, ctx):
149154
def conv0(encoded_value):
150155
value, tz = encoded_value.split()
151156
microseconds = float(value)
152-
tzinfo = SnowflakeConverter._generate_tzinfo_from_tzoffset(
153-
int(tz) - 1440)
157+
tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
154158
try:
155159
t = datetime.fromtimestamp(microseconds, tz=tzinfo)
156160
except OSError as e:
@@ -160,16 +164,15 @@ def conv0(encoded_value):
160164
if pytz.utc != tzinfo:
161165
t += tzinfo.utcoffset(t, is_dst=False)
162166
t = t.replace(tzinfo=tzinfo)
163-
fraction_of_nanoseconds = SnowflakeConverter._adjust_fraction_of_nanoseconds(
167+
fraction_of_nanoseconds = _adjust_fraction_of_nanoseconds(
164168
value, max_fraction, scale)
165169

166170
return format_sftimestamp(ctx, t, fraction_of_nanoseconds)
167171

168172
def conv(encoded_value):
169173
value, tz = encoded_value.split()
170174
microseconds = float(value[0:-scale + 6])
171-
tzinfo = SnowflakeConverter._generate_tzinfo_from_tzoffset(
172-
int(tz) - 1440)
175+
tzinfo = _generate_tzinfo_from_tzoffset(int(tz) - 1440)
173176
try:
174177
t = datetime.fromtimestamp(microseconds, tz=tzinfo)
175178
except OSError as e:
@@ -180,7 +183,7 @@ def conv(encoded_value):
180183
t += tzinfo.utcoffset(t, is_dst=False)
181184
t = t.replace(tzinfo=tzinfo)
182185

183-
fraction_of_nanoseconds = SnowflakeConverter._adjust_fraction_of_nanoseconds(
186+
fraction_of_nanoseconds = _adjust_fraction_of_nanoseconds(
184187
value, max_fraction, scale)
185188

186189
return format_sftimestamp(ctx, t, fraction_of_nanoseconds)

test/test_converter.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import pytz
1111

1212
from snowflake.connector.compat import PY2, IS_WINDOWS
13-
from snowflake.connector.converter import (SnowflakeConverter, ZERO_EPOCH)
13+
from snowflake.connector.converter import (
14+
ZERO_EPOCH,
15+
_generate_tzinfo_from_tzoffset)
1416
from snowflake.connector.converter_snowsql import (SnowflakeConverterSnowSQL)
1517

1618

@@ -34,7 +36,7 @@ def test_fetch_timestamps(conn_cnx):
3436
PST_TZ = "America/Los_Angeles"
3537

3638
tzdiff = 1860 - 1440 # -07:00
37-
tzinfo = SnowflakeConverter._generate_tzinfo_from_tzoffset(tzdiff)
39+
tzinfo = _generate_tzinfo_from_tzoffset(tzdiff)
3840

3941
# TIMESTAMP_TZ
4042
r0 = _compose_tz('1325568896.123456', tzinfo)
@@ -438,3 +440,49 @@ def test_franction_followed_by_year_format(conn_cnx):
438440
'2012-01-03 05:34:56.123456'::TIMESTAMP_NTZ(6)
439441
"""):
440442
assert rec[0] == '05:34:56.123456 Jan 03, 2012'
443+
444+
445+
def test_fetch_fraction_001(conn_cnx):
446+
PST_TZ = "America/Los_Angeles"
447+
448+
converter_class = SnowflakeConverterSnowSQL
449+
sql = """
450+
SELECT
451+
'1900-01-01T05:00:00.000Z'::timestamp_tz(7),
452+
'1900-01-01T05:00:00.000'::timestamp_ntz(7),
453+
'1900-01-01T05:00:01.000Z'::timestamp_tz(7),
454+
'1900-01-01T05:00:01.000'::timestamp_ntz(7),
455+
'1900-01-01T05:00:01.012Z'::timestamp_tz(7),
456+
'1900-01-01T05:00:01.012'::timestamp_ntz(7),
457+
'1900-01-01T05:00:00.012Z'::timestamp_tz(7),
458+
'1900-01-01T05:00:00.012'::timestamp_ntz(7),
459+
'2100-01-01T05:00:00.012Z'::timestamp_tz(7),
460+
'2100-01-01T05:00:00.012'::timestamp_ntz(7),
461+
'1970-01-01T00:00:00Z'::timestamp_tz(7),
462+
'1970-01-01T00:00:00'::timestamp_ntz(7)
463+
"""
464+
with conn_cnx(converter_class=converter_class) as cnx:
465+
cur = cnx.cursor()
466+
cur.execute("""
467+
ALTER SESSION SET TIMEZONE='{tz}';
468+
""".format(tz=PST_TZ))
469+
cur.execute("""
470+
ALTER SESSION SET
471+
TIMESTAMP_OUTPUT_FORMAT='YYYY-MM-DD HH24:MI:SS.FF9 TZH:TZM',
472+
TIMESTAMP_NTZ_OUTPUT_FORMAT='YYYY-MM-DD HH24:MI:SS.FF9',
473+
TIME_OUTPUT_FORMAT='HH24:MI:SS.FF9';
474+
""")
475+
cur.execute(sql)
476+
ret = cur.fetchone()
477+
assert ret[0] == '1900-01-01 05:00:00.000000000 +0000'
478+
assert ret[1] == '1900-01-01 05:00:00.000000000'
479+
assert ret[2] == '1900-01-01 05:00:01.000000000 +0000'
480+
assert ret[3] == '1900-01-01 05:00:01.000000000'
481+
assert ret[4] == '1900-01-01 05:00:01.012000000 +0000'
482+
assert ret[5] == '1900-01-01 05:00:01.012000000'
483+
assert ret[6] == '1900-01-01 05:00:00.012000000 +0000'
484+
assert ret[7] == '1900-01-01 05:00:00.012000000'
485+
assert ret[8] == '2100-01-01 05:00:00.012000000 +0000'
486+
assert ret[9] == '2100-01-01 05:00:00.012000000'
487+
assert ret[10] == '1970-01-01 00:00:00.000000000 +0000'
488+
assert ret[11] == '1970-01-01 00:00:00.000000000'

test/test_converter_more_timestamp.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import pytz
44
from dateutil.parser import parse
55

6-
from snowflake.connector.converter import (SnowflakeConverter, ZERO_EPOCH)
6+
from snowflake.connector.converter import (
7+
_generate_tzinfo_from_tzoffset,
8+
ZERO_EPOCH)
79

810

911
def test_fetch_various_timestamps(conn_cnx):
@@ -34,8 +36,7 @@ def test_fetch_various_timestamps(conn_cnx):
3436
for tz in timezones:
3537
tzdiff = (int(tz[1:3]) * 60 + int(tz[4:6])) * (
3638
-1 if tz[0] == '-' else 1)
37-
tzinfo = SnowflakeConverter._generate_tzinfo_from_tzoffset(
38-
tzdiff)
39+
tzinfo = _generate_tzinfo_from_tzoffset(tzdiff)
3940
try:
4041
ts = datetime.fromtimestamp(float(et), tz=tzinfo)
4142
except OSError:
@@ -54,7 +55,8 @@ def test_fetch_various_timestamps(conn_cnx):
5455
scale = idx + 1
5556
if idx + 1 != 6: # SNOW-28597
5657
try:
57-
ts0 = datetime.fromtimestamp(float(et), tz=tzinfo)
58+
ts0 = datetime.fromtimestamp(float(et),
59+
tz=tzinfo)
5860
except OSError:
5961
ts0 = ZERO_EPOCH + timedelta(seconds=float(et))
6062
if pytz.utc != tzinfo:

test/test_unit_converter.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,13 @@ def test_more_timestamps():
6363
conv.set_parameter('TIMESTAMP_NTZ_OUTPUT_FORMAT',
6464
'YYYY-MM-DD HH24:MI:SS.FF9')
6565
m = conv.to_python_method('TIMESTAMP_NTZ', {'scale': 9})
66-
ret = m('-2208943503.876543211')
67-
assert ret == '1900-01-01 12:34:56.123456789'
66+
assert m('-2208943503.876543211') == '1900-01-01 12:34:56.123456789'
67+
assert m('-2208943503.000000000') == '1900-01-01 12:34:57.000000000'
68+
assert m('-2208943503.012000000') == '1900-01-01 12:34:56.988000000'
69+
70+
conv.set_parameter('TIMESTAMP_NTZ_OUTPUT_FORMAT',
71+
'YYYY-MM-DD HH24:MI:SS.FF9')
72+
m = conv.to_python_method('TIMESTAMP_NTZ', {'scale': 7})
73+
assert m('-2208943503.8765432') == '1900-01-01 12:34:56.123456800'
74+
assert m('-2208943503.0000000') == '1900-01-01 12:34:57.000000000'
75+
assert m('-2208943503.0120000') == '1900-01-01 12:34:56.988000000'

0 commit comments

Comments
 (0)