Skip to content

Commit 874bc10

Browse files
committed
SNOW-26262: Further improvment in formatting TIMESTAMP data in SnowSQL. Pushed up the compile and preformat steps to __init__ as much as possible.
1 parent fcf339b commit 874bc10

File tree

3 files changed

+91
-35
lines changed

3 files changed

+91
-35
lines changed

converter_snowsql.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ def to_python_method(self, type_name, row_type):
5959
try:
6060
fmt = None
6161
if is_timestamp_type_name(type_name):
62-
fmt = SnowflakeDateTimeFormat(self._get_format(type_name))
62+
fmt = SnowflakeDateTimeFormat(
63+
self._get_format(type_name),
64+
datetime_class=SnowflakeDateTime)
6365
elif type_name == u'BINARY':
6466
fmt = SnowflakeBinaryFormat(self._get_format(type_name))
6567
return getattr(self, u'_{type_name}_to_python'.format(

sfdatetime.py

Lines changed: 68 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ def sfdatetime_to_snowflake(value):
110110

111111

112112
class SnowflakeDateTime(UnicodeMixin):
113+
"""
114+
Snowflake DateTime class.
115+
116+
The differene to the native datetime class is Snowflake supports up to
117+
nanoseconds precision.
118+
"""
119+
113120
def __init__(self, ts, nanosecond):
114121
self._datetime = ts
115122
self._nanosecond = nanosecond
@@ -133,57 +140,85 @@ def __bytes__(self):
133140

134141

135142
class SnowflakeDateTimeFormat(object):
136-
def __init__(self, sql_format):
143+
"""
144+
Snowflake DateTime Formatter
145+
"""
146+
147+
def __init__(self, sql_format, datetime_class=datetime, scale=None):
137148
self._sql_format = sql_format
138149
self._fragments = []
150+
self._scale = scale
139151
self.logger = getLogger(__name__)
140152

141153
self._compile()
142-
self._fraction_pos = -1
143154
if len(self._fragments) != 1:
144155
raise errors.InternalError(
145156
u'Only one fragment is allowed {0}'.format(
146157
u','.join(self._fragments)))
147158

148159
self._simple_datetime_pattern = self._to_simple_datetime_pattern()
149160

150-
def python_format(self):
151-
return self._python_format
161+
if self._scale is None and self._fractions_len >= 0:
162+
self._scale = self._fractions_len
163+
else:
164+
self._scale = 6
152165

153-
def format(self, value, scale=6):
154-
if isinstance(value, time.struct_time):
155-
return TO_UNICODE(time.strftime(
156-
self._simple_datetime_pattern, value))
166+
self._nano_str = None
157167
if self._fractions_pos >= 0:
158-
if self._fractions_len >= 0:
159-
scale = self._fractions_len
160-
if isinstance(value, datetime):
161-
nanos = value.microsecond
162-
nano_str = (u"{0:06d}".format(nanos))[:scale]
168+
if issubclass(datetime_class, datetime):
169+
self._nano_str = u"{0:06d}" # milliseconds precision
163170
else:
164-
nanos = value.nanosecond
165-
nano_str = (u"{0:09d}".format(nanos))[:scale]
166-
old_format = self._fragments[0][u'python_format']
171+
self._nano_str = u"{0:09d}" # nanoseconds precision
167172
if self._fractions_with_dot:
168-
nano_str = u'.{0}'.format(nano_str)
169-
new_format = old_format[:self._fractions_pos] + nano_str + \
170-
old_format[self._fractions_pos:]
171-
else:
172-
new_format = self._simple_datetime_pattern
173+
self._nano_str = u'.{0}'.format(self._nano_str)
174+
self._scale += 1
173175

174-
if isinstance(value, SnowflakeDateTime):
175-
if isinstance(value.datetime, time.struct_time):
176-
return TO_UNICODE(time.strftime(new_format, value.datetime))
176+
self.format = getattr(self, u'_format_{type_name}'.format(
177+
type_name=datetime_class.__name__))
178+
179+
def python_format(self):
180+
return self._python_format
181+
182+
def _pre_format(self, value):
183+
updated_format = self._simple_datetime_pattern
184+
185+
if self._nano_str:
186+
if hasattr(value, 'microsecond'):
187+
nanos = value.microsecond
188+
elif hasattr(value, 'nanosecond'):
189+
nanos = value.nanosecond
177190
else:
178-
if value.datetime.year < 1900:
179-
# NOTE: still not supported
180-
return value.datetime.isoformat()
181-
return value.datetime.strftime(new_format)
182-
else:
183-
if value.year < 1900:
184-
# NOTE: still not supported
185-
return value.isoformat()
186-
return value.strftime(new_format)
191+
nanos = 0 # struct_time. no fraction of second
192+
nano_value = self._nano_str.format(nanos)[:self._scale]
193+
updated_format = \
194+
updated_format[:self._fractions_pos] + nano_value + \
195+
updated_format[self._fractions_pos:]
196+
return updated_format
197+
198+
def _format_SnowflakeDateTime(self, value):
199+
"""
200+
Formats SnowflakeDateTime object
201+
"""
202+
updated_format = self._pre_format(value)
203+
if isinstance(value.datetime, time.struct_time):
204+
return TO_UNICODE(time.strftime(
205+
updated_format, value.datetime))
206+
if value.datetime.year < 1900:
207+
# NOTE: still not supported
208+
return value.datetime.isoformat()
209+
return value.datetime.strftime(updated_format)
210+
211+
def _format_datetime(self, value):
212+
"""
213+
Formats datetime object
214+
"""
215+
updated_format = self._pre_format(value)
216+
if isinstance(value, time.struct_time):
217+
return TO_UNICODE(time.strftime(updated_format, value))
218+
if value.year < 1900:
219+
# NOTE: still not supported
220+
return value.isoformat()
221+
return value.strftime(updated_format)
187222

188223
def _to_simple_datetime_pattern(self):
189224
if len(self._fragments) == 1:

test/test_unit_datetime.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,31 @@ def test_datetime_format_negative():
5252

5353

5454
def test_struct_time_format():
55+
# struct_time for general use
5556
value = time.strptime("30 Sep 01 11:20:30", "%d %b %y %H:%M:%S")
5657
formatter = sfdatetime.SnowflakeDateTimeFormat(
5758
u'YYYY-MM-DD"T"HH24:MI:SS.FF')
58-
assert formatter.format(value) == '2001-09-30T11:20:30'
59+
assert formatter.format(value) == '2001-09-30T11:20:30.000000'
5960

61+
# struct_time encapsulated in SnowflakeDateTime. Mainly used by SnowSQL
6062
value = sfdatetime.SnowflakeDateTime(
6163
time.strptime("30 Sep 01 11:20:30", "%d %b %y %H:%M:%S"), nanosecond=0
6264
)
65+
formatter = sfdatetime.SnowflakeDateTimeFormat(
66+
u'YYYY-MM-DD"T"HH24:MI:SS.FF',
67+
datetime_class=sfdatetime.SnowflakeDateTime)
6368
assert formatter.format(value) == '2001-09-30T11:20:30.000000'
69+
70+
# format without fraction of seconds
71+
formatter = sfdatetime.SnowflakeDateTimeFormat(
72+
u'YYYY-MM-DD"T"HH24:MI:SS',
73+
datetime_class=sfdatetime.SnowflakeDateTime)
74+
assert formatter.format(value) == '2001-09-30T11:20:30'
75+
76+
# extreme large epoch time
77+
value = sfdatetime.SnowflakeDateTime(
78+
time.gmtime(14567890123567), nanosecond=0)
79+
formatter = sfdatetime.SnowflakeDateTimeFormat(
80+
u'YYYY-MM-DD"T"HH24:MI:SS.FF',
81+
datetime_class=sfdatetime.SnowflakeDateTime)
82+
assert formatter.format(value) == '463608-01-23T09:26:07.000000'

0 commit comments

Comments
 (0)