如何在 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.getsizeofdateObj是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'0x6d0x016d是365,所以这是令人鼓舞的。

test_value = '1900-01-01 00:00:01'(纪元后 1 秒)returned '\x00\x00\x00\x00,\x01\x00\x00'','0x2c0x012c 是 300。

test_value = '1900-01-01 00:00:02'(纪元后 2 秒)returned '\x00\x00\x00\x00X\x02\x00\x00''X'0x580x0258 是 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]

这似乎起到了作用。