如何在 pyodbc 输出转换器函数中解压 SQL Server DATETIME?
How do I unpack a SQL Server DATETIME in a pyodbc Output Converter function?
我正在向 pyodbc
连接对象添加输出转换器以处理从 SQL 服务器返回的日期类型。我能够使用以下命令解压 datetime.time
结构:
tuple = struct.unpack("HHHI", dateObj)
效果很好。不过,我无法弄清楚 datetime.datetime
对象的秘诀,根据 pyodbc 文档,它是一个 TIMESTAMP_STRUCT,定义为 here:
typedef struct tagTIMESTAMP_STRUCT
{
SQLSMALLINT year;
SQLUSMALLINT month;
SQLUSMALLINT day;
SQLUSMALLINT hour;
SQLUSMALLINT minute;
SQLUSMALLINT second;
SQLUINTEGER fraction;
} TIMESTAMP_STRUCT;
数据库中该列的数据是 2018-01-11 11:50:16.000
,并且没有 add_output_convert
陷阱 pyodbc returns:
TypeError: datetime.datetime(2018, 1, 11, 11, 50, 16) is not JSON serializable
看起来 pyodbc 默默地删除了分数,这很好。 unpack()
格式不应该是以下之一:
tuple = struct.unpack("hHHHHHI", dateObj) # with missing fraction
tuple = struct.unpack("hHHHHH", dateObj)
?后者只是 returns:
error: unpack requires a string argument of length 12
备案,根据sys.getsizeof
dateObj是41字节。对格式有什么建议吗?这是 Windows 10 64 位,以及 Linux 64 位。
你似乎一直在追踪一些错误的线索。 SQL 服务器 ODBC 驱动程序没有 return 41 个字节的数据用于 DATETIME 值,它只有 returns 8 个字节。 (sys.getsizeof
return 的值为 41,因为它包含与垃圾回收相关的 "overhead"。)而且这 8 个字节不可能代表 TIMESTAMP_STRUCT,所以它必须是某种东西否则。
从基本测试开始...
import pyodbc
def datetime_as_string(raw_bytes):
return raw_bytes
cnxn = pyodbc.connect('DSN=SQLmyDb;', autocommit=True)
cnxn.add_output_converter(pyodbc.SQL_TYPE_TIMESTAMP, datetime_as_string)
crsr = cnxn.cursor()
test_value = '2018-01-11 11:50:16'
rtn = crsr.execute("SELECT CAST(? AS DATETIME)", test_value).fetchval()
print(repr(rtn))
crsr.close()
cnxn.close()
...我看到你的测试值是用'e\xa8\x00\x00\xa0\x14\xc3\x00'
表示的。在 Windows 计算器中使用十六进制转换器花了一点时间,我发现内容不是很明显。凭直觉,我尝试了 test_value = '1900-01-01 00:00:00'
和 returned '\x00\x00\x00\x00\x00\x00\x00\x00'
所以至少我有一个开始的地方( "epoch" 用于 SQL Server DATETIME 值) .
test_value = '1901-01-01 00:00:00'
(纪元后1年)returned 'm\x01\x00\x00\x00\x00\x00\x00'
,'m'
是0x6d
,0x016d
是365,所以这是令人鼓舞的。
test_value = '1900-01-01 00:00:01'
(纪元后 1 秒)returned '\x00\x00\x00\x00,\x01\x00\x00'
,','
是 0x2c
,0x012c
是 300。
test_value = '1900-01-01 00:00:02'
(纪元后 2 秒)returned '\x00\x00\x00\x00X\x02\x00\x00'
,'X'
是 0x58
,0x0258
是 600。
所以 SQL 服务器 ODBC 驱动程序是 returning 两个 4 字节有符号整数,第一个是整日中距纪元的偏移量,第二个是部分日的增量1/300 秒。
因此我将输出转换器功能更改为
def datetime_as_string(raw_bytes):
tup = struct.unpack("<2l", raw_bytes)
days_since_1900 = tup[0]
partial_day = round(tup[1] / 300.0, 3)
date_time = datetime(1900, 1, 1) + timedelta(days=days_since_1900) + timedelta(seconds=partial_day)
return date_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:23]
这似乎起到了作用。
我正在向 pyodbc
连接对象添加输出转换器以处理从 SQL 服务器返回的日期类型。我能够使用以下命令解压 datetime.time
结构:
tuple = struct.unpack("HHHI", dateObj)
效果很好。不过,我无法弄清楚 datetime.datetime
对象的秘诀,根据 pyodbc 文档,它是一个 TIMESTAMP_STRUCT,定义为 here:
typedef struct tagTIMESTAMP_STRUCT
{
SQLSMALLINT year;
SQLUSMALLINT month;
SQLUSMALLINT day;
SQLUSMALLINT hour;
SQLUSMALLINT minute;
SQLUSMALLINT second;
SQLUINTEGER fraction;
} TIMESTAMP_STRUCT;
数据库中该列的数据是 2018-01-11 11:50:16.000
,并且没有 add_output_convert
陷阱 pyodbc returns:
TypeError: datetime.datetime(2018, 1, 11, 11, 50, 16) is not JSON serializable
看起来 pyodbc 默默地删除了分数,这很好。 unpack()
格式不应该是以下之一:
tuple = struct.unpack("hHHHHHI", dateObj) # with missing fraction
tuple = struct.unpack("hHHHHH", dateObj)
?后者只是 returns:
error: unpack requires a string argument of length 12
备案,根据sys.getsizeof
dateObj是41字节。对格式有什么建议吗?这是 Windows 10 64 位,以及 Linux 64 位。
你似乎一直在追踪一些错误的线索。 SQL 服务器 ODBC 驱动程序没有 return 41 个字节的数据用于 DATETIME 值,它只有 returns 8 个字节。 (sys.getsizeof
return 的值为 41,因为它包含与垃圾回收相关的 "overhead"。)而且这 8 个字节不可能代表 TIMESTAMP_STRUCT,所以它必须是某种东西否则。
从基本测试开始...
import pyodbc
def datetime_as_string(raw_bytes):
return raw_bytes
cnxn = pyodbc.connect('DSN=SQLmyDb;', autocommit=True)
cnxn.add_output_converter(pyodbc.SQL_TYPE_TIMESTAMP, datetime_as_string)
crsr = cnxn.cursor()
test_value = '2018-01-11 11:50:16'
rtn = crsr.execute("SELECT CAST(? AS DATETIME)", test_value).fetchval()
print(repr(rtn))
crsr.close()
cnxn.close()
...我看到你的测试值是用'e\xa8\x00\x00\xa0\x14\xc3\x00'
表示的。在 Windows 计算器中使用十六进制转换器花了一点时间,我发现内容不是很明显。凭直觉,我尝试了 test_value = '1900-01-01 00:00:00'
和 returned '\x00\x00\x00\x00\x00\x00\x00\x00'
所以至少我有一个开始的地方( "epoch" 用于 SQL Server DATETIME 值) .
test_value = '1901-01-01 00:00:00'
(纪元后1年)returned 'm\x01\x00\x00\x00\x00\x00\x00'
,'m'
是0x6d
,0x016d
是365,所以这是令人鼓舞的。
test_value = '1900-01-01 00:00:01'
(纪元后 1 秒)returned '\x00\x00\x00\x00,\x01\x00\x00'
,','
是 0x2c
,0x012c
是 300。
test_value = '1900-01-01 00:00:02'
(纪元后 2 秒)returned '\x00\x00\x00\x00X\x02\x00\x00'
,'X'
是 0x58
,0x0258
是 600。
所以 SQL 服务器 ODBC 驱动程序是 returning 两个 4 字节有符号整数,第一个是整日中距纪元的偏移量,第二个是部分日的增量1/300 秒。
因此我将输出转换器功能更改为
def datetime_as_string(raw_bytes):
tup = struct.unpack("<2l", raw_bytes)
days_since_1900 = tup[0]
partial_day = round(tup[1] / 300.0, 3)
date_time = datetime(1900, 1, 1) + timedelta(days=days_since_1900) + timedelta(seconds=partial_day)
return date_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:23]
这似乎起到了作用。