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

Omo*_*tis 5 python sql-server pyodbc python-2.7

我正在将输出转换器添加到pyodbc连接对象以处理从 SQL Server 返回的日期类型。我能够使用以下命令解压缩datetime.time结构:

tuple   = struct.unpack("HHHI", dateObj)
Run Code Online (Sandbox Code Playgroud)

效果很好。我想不通的秘密武器的datetime.datetime对象,不过,根据pyodbc文档这是一个TIMESTAMP_STRUCT,定义在这里

typedef struct tagTIMESTAMP_STRUCT
{
        SQLSMALLINT    year;
        SQLUSMALLINT   month;
        SQLUSMALLINT   day;
        SQLUSMALLINT   hour;
        SQLUSMALLINT   minute;
        SQLUSMALLINT   second;
        SQLUINTEGER    fraction;
} TIMESTAMP_STRUCT;
Run Code Online (Sandbox Code Playgroud)

数据库中该列的数据是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)
Run Code Online (Sandbox Code Playgroud)

? 后者简单地返回:

error: unpack requires a string argument of length 12

对于记录,根据sys.getsizeofdateObj 是41 个字节。对格式有什么建议吗?这是 Windows 10 64 位,以及 Linux 64 位。

Gor*_*son 6

你似乎一直在追踪一些虚假的线索。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()
Run Code Online (Sandbox Code Playgroud)

...我看到您的测试值由'e\xa8\x00\x00\xa0\x14\xc3\x00'. 使用 Windows Calculator 中的十六进制转换器一段时间后,我发现内容并不是很明显。凭着预感,我试了一下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]
Run Code Online (Sandbox Code Playgroud)

这似乎奏效了。