如何在pyodbc输出转换器函数中解压缩SQL Server DATETIME?

时间:2018-01-19 17:01:40

标签: python sql-server python-2.7 pyodbc

我将输出转换器添加到pyodbc连接对象以处理从SQL Server返回的日期类型。我能够使用:

解压缩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;

db中该列的数据为2018-01-11 11:50:16.000,没有add_output_convert陷阱pyodbc返回:

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)

?后者只是返回:

error: unpack requires a string argument of length 12

对于记录,根据sys.getsizeof dateObj是41个字节。有关格式的任何建议吗?这是Windows 10 64位,以及Linux 64位。

1 个答案:

答案 0 :(得分:3)

你似乎一直在追逐一些虚假的线索。 SQL Server ODBC驱动程序不为DATETIME值返回41个字节的数据,它只返回8个字节。 (sys.getsizeof返回值41,因为它包含与垃圾收集相关的“开销”。)并且这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'并返回'\x00\x00\x00\x00\x00\x00\x00\x00',因此至少我有一个起点(SQL Server DATETIME值的“纪元”)。

test_value = '1901-01-01 00:00:00'(在纪元后1年)返回'm\x01\x00\x00\x00\x00\x00\x00''m'0x6d0x016d为365,因此令人鼓舞。

test_value = '1900-01-01 00:00:01'(在纪元后1秒)返回'\x00\x00\x00\x00,\x01\x00\x00'','0x2c0x012c为300。

test_value = '1900-01-01 00:00:02'(纪元后2秒)返回'\x00\x00\x00\x00X\x02\x00\x00''X'0x580x0258为600。

因此,SQL Server ODBC驱动程序返回两个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]

这似乎可以解决问题。