Skip to content

Commit 132112e

Browse files
jahnvi480Sumit Sarabhai
authored andcommitted
Merged PR 5353: Adding datetime2 and smalldatetime
#### AI description (iteration 1) #### PR Classification New feature #### PR Summary This pull request adds support for `datetime2` and `smalldatetime` data types. - `mssql_python/cursor.py`: Added parsing and handling for `datetime2` and `smalldatetime` data types. - `tests/test_004_cursor.py`: Added tests for parsing and inserting `datetime2` and `smalldatetime` columns. - `mssql_python/helpers.py`: Minor documentation update. <!-- GitOpsUserAgent=GitOps.Apps.Server.pullrequestcopilot --> Related work items: #34049
1 parent 55fa7e4 commit 132112e

File tree

3 files changed

+75
-116
lines changed

3 files changed

+75
-116
lines changed

mssql_python/cursor.py

Lines changed: 9 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def _parse_date(self, param):
105105

106106
def _parse_datetime(self, param):
107107
"""
108-
Attempt to parse a string as a datetime.
108+
Attempt to parse a string as a datetime, smalldatetime, datetime2, timestamp.
109109
110110
Args:
111111
param: The string to parse.
@@ -116,24 +116,12 @@ def _parse_datetime(self, param):
116116
formats = [
117117
"%Y-%m-%dT%H:%M:%S.%f", # ISO 8601 datetime with fractional seconds
118118
"%Y-%m-%dT%H:%M:%S", # ISO 8601 datetime
119-
"%Y-%m-%d %H:%M:%S.%f", # Datetime with fractional seconds (up to 3 digits)
119+
"%Y-%m-%d %H:%M:%S.%f", # Datetime with fractional seconds
120120
"%Y-%m-%d %H:%M:%S", # Datetime without fractional seconds
121121
]
122122
for fmt in formats:
123123
try:
124-
dt = datetime.datetime.strptime(param, fmt)
125-
126-
# If there are fractional seconds, ensure they do not exceed 7 digits
127-
if fmt in ["%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S.%f"]:
128-
fractional_part = param.split(".")[-1]
129-
# If the fractional part is more than 3 digits, truncate to 3 digits
130-
if len(fractional_part) > 3:
131-
fractional_part = fractional_part[:3]
132-
# Convert to microseconds
133-
dt = dt.replace(
134-
microsecond=int(fractional_part.ljust(3, "0")) * 1000
135-
)
136-
return dt # Valid datetime
124+
return datetime.datetime.strptime(param, fmt) # Valid datetime
137125
except ValueError:
138126
continue # Try next format
139127

@@ -160,69 +148,6 @@ def _parse_time(self, param):
160148
continue
161149
return None
162150

163-
# def _parse_timestamptz(self, param):
164-
# """
165-
# Attempt to parse a string as a timestamp with time zone (timestamptz).
166-
#
167-
# Args:
168-
# param: The string to parse.
169-
#
170-
# Returns:
171-
# A datetime.datetime object if parsing is successful, else None.
172-
# """
173-
# formats = [
174-
# "%Y-%m-%dT%H:%M:%S%z", # ISO 8601 datetime with timezone offset
175-
# "%Y-%m-%d %H:%M:%S.%f%z", # Datetime with fractional seconds and timezone offset
176-
# ]
177-
# for fmt in formats:
178-
# try:
179-
# return datetime.datetime.strptime(param, fmt)
180-
# except ValueError:
181-
# continue
182-
# return None
183-
184-
# def _parse_smalldatetime(self, param):
185-
# """
186-
# Attempt to parse a string as a smalldatetime.
187-
#
188-
# Args:
189-
# param: The string to parse.
190-
#
191-
# Returns:
192-
# A datetime.datetime object if parsing is successful, else None.
193-
# """
194-
# formats = [
195-
# "%Y-%m-%d %H:%M:%S", # Standard datetime
196-
# ]
197-
# for fmt in formats:
198-
# try:
199-
# return datetime.datetime.strptime(param, fmt)
200-
# except ValueError:
201-
# continue
202-
# return None
203-
204-
# def _parse_datetime2(self, param):
205-
# """
206-
# Attempt to parse a string as a datetime2.
207-
#
208-
# Args:
209-
# param: The string to parse.
210-
#
211-
# Returns:
212-
# A datetime.datetime object if parsing is successful, else None.
213-
# """
214-
# formats = [
215-
# "%Y-%m-%d %H:%M:%S.%f", # Datetime with fractional seconds (up to 6 digits)
216-
# ]
217-
# for fmt in formats:
218-
# try:
219-
# dt = datetime.datetime.strptime(param, fmt)
220-
# if fmt == "%Y-%m-%d %H:%M:%S.%f" and len(param.split('.')[-1]) > 3:
221-
# return dt
222-
# except ValueError:
223-
# continue
224-
# return None
225-
226151
def _get_numeric_data(self, param):
227152
"""
228153
Get the data for a numeric parameter.
@@ -290,7 +215,7 @@ def _map_sql_type(self, param, parameters_list, i):
290215
"""
291216
if param is None:
292217
return (
293-
ddbc_sql_const.SQL_NULL_DATA.value,
218+
ddbc_sql_const.SQL_VARCHAR.value, # TODO: Add SQLDescribeParam to get correct type
294219
ddbc_sql_const.SQL_C_DEFAULT.value,
295220
1,
296221
0,
@@ -360,7 +285,7 @@ def _map_sql_type(self, param, parameters_list, i):
360285
0,
361286
)
362287

363-
# Attempt to parse as date, datetime or time
288+
# Attempt to parse as date, datetime, datetime2, timestamp, smalldatetime or time
364289
if self._parse_date(param):
365290
parameters_list[i] = self._parse_date(
366291
param
@@ -376,8 +301,8 @@ def _map_sql_type(self, param, parameters_list, i):
376301
return (
377302
ddbc_sql_const.SQL_TIMESTAMP.value,
378303
ddbc_sql_const.SQL_C_TYPE_TIMESTAMP.value,
379-
23,
380-
3,
304+
26,
305+
6,
381306
)
382307
if self._parse_time(param):
383308
parameters_list[i] = self._parse_time(param)
@@ -453,8 +378,8 @@ def _map_sql_type(self, param, parameters_list, i):
453378
return (
454379
ddbc_sql_const.SQL_TIMESTAMP.value,
455380
ddbc_sql_const.SQL_C_TYPE_TIMESTAMP.value,
456-
23,
457-
3,
381+
26,
382+
6,
458383
)
459384

460385
if isinstance(param, datetime.date):

mssql_python/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def check_error(handle_type, handle, ret):
6262
Args:
6363
handle_type: The type of the handle (e.g., SQL_HANDLE_ENV, SQL_HANDLE_DBC).
6464
handle: The handle to check for errors.
65-
ret: The return code from the ODBC function call.
65+
ret: The return code from the DDBC function call.
6666
6767
Raises:
6868
RuntimeError: If an error is found.

tests/test_004_cursor.py

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,40 @@ def test_insert_datetime_column(cursor, db_connection):
148148
cursor.execute("DROP TABLE single_column")
149149
db_connection.commit()
150150

151+
def test_insert_datetime2_column(cursor, db_connection):
152+
"""Test inserting data into the datetime_column"""
153+
try:
154+
drop_table_if_exists(cursor, "single_column")
155+
cursor.execute("CREATE TABLE single_column (datetime2_column DATETIME2)")
156+
db_connection.commit()
157+
cursor.execute("INSERT INTO single_column (datetime2_column) VALUES (?)", [datetime(2024, 5, 20, 12, 34, 56, 123456)])
158+
db_connection.commit()
159+
cursor.execute("SELECT datetime2_column FROM single_column")
160+
row = cursor.fetchone()
161+
assert row[0] == datetime(2024, 5, 20, 12, 34, 56, 123456), "Datetime2 column insertion/fetch failed"
162+
except Exception as e:
163+
pytest.fail(f"Datetime2 column insertion/fetch failed: {e}")
164+
finally:
165+
cursor.execute("DROP TABLE single_column")
166+
db_connection.commit()
167+
168+
def test_insert_smalldatetime_column(cursor, db_connection):
169+
"""Test inserting data into the datetime_column"""
170+
try:
171+
drop_table_if_exists(cursor, "single_column")
172+
cursor.execute("CREATE TABLE single_column (smalldatetime_column SMALLDATETIME)")
173+
db_connection.commit()
174+
cursor.execute("INSERT INTO single_column (smalldatetime_column) VALUES (?)", [datetime(2024, 5, 20, 12, 34)])
175+
db_connection.commit()
176+
cursor.execute("SELECT smalldatetime_column FROM single_column")
177+
row = cursor.fetchone()
178+
assert row[0] == datetime(2024, 5, 20, 12, 34), "Smalldatetime column insertion/fetch failed"
179+
except Exception as e:
180+
pytest.fail(f"Smalldatetime column insertion/fetch failed: {e}")
181+
finally:
182+
cursor.execute("DROP TABLE single_column")
183+
db_connection.commit()
184+
151185
def test_insert_date_column(cursor, db_connection):
152186
"""Test inserting data into the date_column"""
153187
try:
@@ -955,37 +989,37 @@ def test_parse_time(cursor, db_connection):
955989
cursor.execute("DROP TABLE pytest_time_test")
956990
db_connection.commit()
957991

958-
# def test_parse_smalldatetime(cursor, db_connection):
959-
# """Test _parse_smalldatetime"""
960-
# try:
961-
# cursor.execute("CREATE TABLE pytest_smalldatetime_test (smalldatetime_column SMALLDATETIME)")
962-
# db_connection.commit()
963-
# cursor.execute("INSERT INTO pytest_smalldatetime_test (smalldatetime_column) VALUES (?)", ['2024-05-20 12:34:56'])
964-
# db_connection.commit()
965-
# cursor.execute("SELECT smalldatetime_column FROM pytest_smalldatetime_test")
966-
# row = cursor.fetchone()
967-
# assert row[0] == datetime(2024, 5, 20, 12, 34), "Smalldatetime parsing failed"
968-
# except Exception as e:
969-
# pytest.fail(f"Smalldatetime parsing test failed: {e}")
970-
# finally:
971-
# cursor.execute("DROP TABLE pytest_smalldatetime_test")
972-
# db_connection.commit()
973-
974-
# def test_parse_datetime2(cursor, db_connection):
975-
# """Test _parse_datetime2"""
976-
# try:
977-
# cursor.execute("CREATE TABLE pytest_datetime2_test (datetime2_column DATETIME2)")
978-
# db_connection.commit()
979-
# cursor.execute("INSERT INTO pytest_datetime2_test (datetime2_column) VALUES (?)", ['2024-05-20 12:34:56.123456'])
980-
# db_connection.commit()
981-
# cursor.execute("SELECT datetime2_column FROM pytest_datetime2_test")
982-
# row = cursor.fetchone()
983-
# assert row[0] == datetime(2024, 5, 20, 12, 34, 56, 123456), "Datetime2 parsing failed"
984-
# except Exception as e:
985-
# pytest.fail(f"Datetime2 parsing test failed: {e}")
986-
# finally:
987-
# cursor.execute("DROP TABLE pytest_datetime2_test")
988-
# db_connection.commit()
992+
def test_parse_smalldatetime(cursor, db_connection):
993+
"""Test _parse_smalldatetime"""
994+
try:
995+
cursor.execute("CREATE TABLE pytest_smalldatetime_test (smalldatetime_column SMALLDATETIME)")
996+
db_connection.commit()
997+
cursor.execute("INSERT INTO pytest_smalldatetime_test (smalldatetime_column) VALUES (?)", ['2024-05-20 12:34'])
998+
db_connection.commit()
999+
cursor.execute("SELECT smalldatetime_column FROM pytest_smalldatetime_test")
1000+
row = cursor.fetchone()
1001+
assert row[0] == datetime(2024, 5, 20, 12, 34), "Smalldatetime parsing failed"
1002+
except Exception as e:
1003+
pytest.fail(f"Smalldatetime parsing test failed: {e}")
1004+
finally:
1005+
cursor.execute("DROP TABLE pytest_smalldatetime_test")
1006+
db_connection.commit()
1007+
1008+
def test_parse_datetime2(cursor, db_connection):
1009+
"""Test _parse_datetime2"""
1010+
try:
1011+
cursor.execute("CREATE TABLE pytest_datetime2_test (datetime2_column DATETIME2)")
1012+
db_connection.commit()
1013+
cursor.execute("INSERT INTO pytest_datetime2_test (datetime2_column) VALUES (?)", ['2024-05-20 12:34:56.123456'])
1014+
db_connection.commit()
1015+
cursor.execute("SELECT datetime2_column FROM pytest_datetime2_test")
1016+
row = cursor.fetchone()
1017+
assert row[0] == datetime(2024, 5, 20, 12, 34, 56, 123456), "Datetime2 parsing failed"
1018+
except Exception as e:
1019+
pytest.fail(f"Datetime2 parsing test failed: {e}")
1020+
finally:
1021+
cursor.execute("DROP TABLE pytest_datetime2_test")
1022+
db_connection.commit()
9891023

9901024
def test_get_numeric_data(cursor, db_connection):
9911025
"""Test _get_numeric_data"""

0 commit comments

Comments
 (0)