返回

Python DolphinDB 毫秒时间戳:解决精度丢失与UTC问题

python

Python 向 DolphinDB 流表插入带毫秒精度的本地时间:告别精度丢失与 UTC 困扰

使用 Python 向 DolphinDB 流表写入数据时,若想记录事件发生的精确本地时间(带毫秒),可能会遇到两个比较挠头的问题:

  1. 毫秒精度丢失 :直接用 numpy.datetime64('now', 'ms') 生成时间戳,毫秒部分经常显示为 .000,丢失了实际的毫秒信息。
  2. UTC 与本地时间混淆numpy.datetime64('now') 或者类似函数默认给的是 UTC 时间,并非我们系统当前显示的本地时间。

举个例子,假设你在 DolphinDB 中创建了这样一个流数据表 demoSt

st = streamTable(
    array(LONG, 0) as id,
    array(TIMESTAMP, 0) as ts,    // 用于存储高精度时间戳
    array(DATETIME, 0) as dt,     // 用于存储日期时间
    array(DOUBLE, 0) as value
)
share(st, "demoSt")

然后尝试用下面的 Python 代码插入数据:

import dolphindb as ddb
import numpy as np
import pandas as pd

s = ddb.session()
# 请替换为你的 DolphinDB 连接信息
s.connect("192.168.1.125", 8848, "admin", "123456")

# 尝试插入当前时间
# 注意:这行代码存在问题
s.run("tableInsert{demoSt}", [1, np.datetime64('now', "ms"), np.datetime64('now'), 0.01])

# 查询确认一下
print(s.run("select * from demoSt"))
s.close()

结果往往不符合预期,ts 列的毫秒是 000,而且 tsdt 都可能是 UTC 时间。

错误的毫秒和UTC时间

那到底该怎么获取和插入正确的、带毫秒精度的本地时间呢?

问题根源在哪?

简单来说,这两个问题源于 Python 中处理时间的方式以及 numpy.datetime64 的一些特性:

  1. 毫秒精度问题np.datetime64('now') 的实际精度可能依赖于操作系统和 NumPy 版本。直接指定 'ms' 单位有时并不能保证它会从系统时钟 捕获 毫秒级的精度,更像是在现有时间基础上做了一个单位标记或截断,导致毫秒部分为零。
  2. 时区问题np.datetime64 类型本身是“时区不感知”(timezone-naive)的。'now' 通常获取的是 POSIX 时间戳(或与之类似的 UTC 基准),它不包含本地时区信息。即使你的操作系统显示本地时间,NumPy 的这个函数默认不应用本地时区偏移。

解决方案

别担心,解决这个问题有几种途径,可以根据你的具体需求和偏好来选择。

方案一:利用 Python datetime 模块 (推荐)

Python 标准库里的 datetime 模块是处理日期时间的强大工具,它能轻松获取带微秒(比毫秒更精细)的本地时间。

原理与作用:

datetime.datetime.now() 函数直接从操作系统获取当前的本地日期和时间,精度可以达到微秒级别。DolphinDB 的 Python API 能够识别 Python 的 datetime 对象,并自动将其转换为 DolphinDB 合适的时间类型(TIMESTAMPDATETIME)。对于 TIMESTAMP 类型,API 会尽量保留其精度;对于 DATETIME 类型,则会截断到秒。

操作步骤与代码示例:

import dolphindb as ddb
import numpy as np
import pandas as pd
import datetime # 导入 datetime 模块

s = ddb.session()
# 请替换为你的 DolphinDB 连接信息
s.connect("192.168.1.125", 8848, "admin", "123456")

# 1. 获取当前本地时间(包含微秒)
current_local_time = datetime.datetime.now()
print(f"Python 获取到的本地时间: {current_local_time}")

# 2. 插入数据
# 直接将 datetime 对象传给 DolphinDB
# API 会自动处理类型转换
s.run("tableInsert{demoSt}", [101, current_local_time, current_local_time, 0.01])

# 查询确认
print("查询插入后的数据:")
print(s.run("select * from demoSt where id=101"))

s.close()

执行结果分析:

运行上述代码,你会看到 DolphinDB 表中的 ts 列现在有了精确到毫秒(甚至更高精度,取决于 DolphinDB TIMESTAMP 内部表示)的时间,并且 dt 列也反映了正确的本地日期和时间(截断到秒)。

Python 获取到的本地时间: 2023-10-27 15:30:45.123456 # 示例输出
查询插入后的数据:
   id        ts                           dt                  value
   --        --                           --                  -----
  101 2023.10.27T15:30:45.123   2023.10.27T15:30:45    0.01

注意事项:

  • datetime.datetime.now() 返回的是“时区不感知”(naive)的本地时间。这意味着它仅代表你运行脚本的机器上的时间,不包含具体的时区信息(如 'Asia/Shanghai' 或 'UTC+8')。
  • 如果你的 Python 脚本运行环境和 DolphinDB 服务器位于不同的时区,并且你需要严格区分或转换时区,事情会复杂一些。你可能需要使用带时区信息(timezone-aware)的 datetime 对象(见方案三)。但对于仅需记录脚本执行时的本地时间,此方法简单有效。

方案二:在 DolphinDB 端生成时间戳

与其在 Python 里获取时间再传给 DolphinDB,不如直接让 DolphinDB 服务器自己生成时间戳。

原理与作用:

DolphinDB 提供了内置函数来获取服务器时间。

  • now(true):获取服务器当前 UTC 时间的 TIMESTAMP,精度可达纳秒。参数 true 是关键,表示需要高精度。
  • localtime(timestamp):将一个 UTC 时间戳转换为服务器所在时区的本地 DATETIME(精度到秒)。

通过在 s.run() 中直接调用这些函数,可以避免网络传输延迟和客户端时钟误差对时间戳精度的影响。

操作步骤与代码示例:

import dolphindb as ddb
import numpy as np
import pandas as pd

s = ddb.session()
# 请替换为你的 DolphinDB 连接信息
s.connect("192.168.1.125", 8848, "admin", "123456")

# 1. 准备要插入的数据,时间字段用 DolphinDB 函数生成
insert_script = """
tableInsert(demoSt, 
    102,              // id
    now(true),        // ts: 使用 DolphinDB now(true) 获取高精度 UTC TIMESTAMP
    localtime(now()), // dt: 使用 DolphinDB localtime(now()) 获取本地 DATETIME
    0.02              // value
)
"""

# 2. 执行插入脚本
s.run(insert_script)

# 3. 或者分开获取再插入 (这种方式更灵活,但会多一次网络交互)
# id_val = 103
# ts_val = s.run('now(true)')          # 获取 DDB 服务器的高精度 UTC 时间戳
# dt_val = s.run('localtime(now())') # 获取 DDB 服务器的本地日期时间
# value_val = 0.03
# s.run("tableInsert{demoSt}", [id_val, ts_val, dt_val, value_val])


# 查询确认
print("查询插入后的数据:")
print(s.run("select * from demoSt where id=102"))
# print(s.run("select * from demoSt where id=103"))

s.close()

执行结果分析:

这种方法插入的数据,ts 列将包含 DolphinDB 服务器提供的高精度 UTC 时间戳(注意,TIMESTAMP 类型在 DolphinDB 内部通常存储为 UTC),而 dt 列将包含服务器所在时区的本地时间(精度到秒)。

查询插入后的数据:
   id        ts                           dt                  value
   --        --                           --                  -----
  102 2023.10.27T07:35:10.987   2023.10.27T15:35:10    0.02
   # 假设服务器时区为 UTC+8,ts 显示 UTC,dt 显示本地时间

优势:

  • 时间戳由数据存储端生成,精度高,且不易受客户端与服务器间网络延迟或时钟不同步的影响。
  • 在高频数据写入场景下,可以减少 Python 端获取时间的系统调用开销。

进阶使用:

  • 如果你需要在 TIMESTAMP 列也存储本地时间对应的高精度值(虽然 DolphinDB TIMESTAMP 通常理解为 UTC),需要自行计算本地时间与 UTC 的偏移量,并在 Python 端构造对应的时间戳,或者在 DolphinDB 端进行更复杂的转换。不过,一般推荐 TIMESTAMP 存 UTC,DATETIME 存需要的本地展示时间。
  • DolphinDB 有丰富的时间函数,可以满足更复杂的服务器端时间处理需求。

方案三:使用时区感知的 datetime 对象 (处理跨时区场景)

当你的 Python 应用和 DolphinDB 服务器不在同一个时区,或者你需要严格保证时间记录符合某个特定时区(比如业务规定的时区),就需要用到时区感知(timezone-aware)的 datetime 对象。

原理与作用:

通过 zoneinfo 模块(Python 3.9+)或第三方库 pytz,可以创建带有明确时区信息的 datetime 对象。当把这样的对象传递给 DolphinDB Python API 时,API 通常能理解其时区信息,并可能将其正确转换为 DolphinDB 内部存储的 UTC TIMESTAMP

操作步骤与代码示例 (使用 zoneinfo):

import dolphindb as ddb
import numpy as np
import pandas as pd
import datetime
from zoneinfo import ZoneInfo # Python 3.9+ 标准库

s = ddb.session()
# 请替换为你的 DolphinDB 连接信息
s.connect("192.168.1.125", 8848, "admin", "123456")

# 1. 定义你需要的本地时区
# 例如,使用 'Asia/Shanghai' 时区
# 你可以从 IANA 时区数据库名称中选择 (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
try:
    local_tz = ZoneInfo("Asia/Shanghai")
except ZoneInfo.ZoneInfoNotFoundError:
    print("时区名称无效,请检查。")
    s.close()
    exit()


# 2. 获取带有时区的当前本地时间
aware_local_time = datetime.datetime.now(local_tz)
print(f"Python 获取到的带时区的本地时间: {aware_local_time}")

# 3. 插入数据
# DolphinDB API 会处理带时区的 datetime 对象
# 它通常会转换成等效的 UTC 时间戳存入 TIMESTAMP 列
# 对于 DATETIME 列,行为可能依赖具体 API 版本和 DDB 设置,
# 但一般也会基于其 UTC 等价值或截断到秒的本地时间。
s.run("tableInsert{demoSt}", [104, aware_local_time, aware_local_time, 0.04])


# 查询确认
print("查询插入后的数据:")
print(s.run("select * from demoSt where id=104"))

s.close()

使用 pytz (如果你的 Python 版本低于 3.9):

# 需要先安装 pip install pytz
import pytz

# ... (连接代码同上) ...

# 1. 定义时区
local_tz = pytz.timezone("Asia/Shanghai")

# 2. 获取带时区的当前本地时间 (注意 pytz 的用法稍有不同)
naive_now = datetime.datetime.now()
aware_local_time = local_tz.localize(naive_now)
# 或者获取 UTC 时间再转换到本地时区
# aware_local_time = datetime.datetime.now(pytz.utc).astimezone(local_tz)
print(f"Python (pytz) 获取到的带时区的本地时间: {aware_local_time}")


# ... (插入和查询代码同上) ...

注意事项:

  • 这种方法最精确地表达了时间的“含义”(是哪个时区的几点几分)。
  • 你需要确保使用的时区名称正确无误。
  • 数据库如何存储和展示时区感知的时间戳可能需要你查阅 DolphinDB 的具体文档或进行测试,TIMESTAMP 列几乎总是存储为 UTC 等价值。
  • 务必保持 tzdata (操作系统层面或 Python 包如 pytz) 更新,以应对全球时区和夏令时规则的变化。

总结一下

要在 Python 中向 DolphinDB 插入带毫秒精度的本地时间,主要有以下选择:

  1. 推荐使用 datetime.datetime.now() :简单直接,适用于脚本和服务器同处本地时区的场景,能够获取并插入带毫秒(实际是微秒)精度的本地时间。
  2. 推荐在 DolphinDB 端生成时间戳 (now(true), localtime(now())) :性能好,精度由服务器保证,TIMESTAMP 存精确 UTC,DATETIME 存本地时间,适合高频写入和对服务器时间一致性要求高的场景。
  3. 使用时区感知的 datetime (配合 zoneinfopytz) :最严谨,适合跨时区部署或需要强制指定业务时区的场景。

根据你的具体场景——是否需要绝对的本地时间展示、是否关心微秒级的极致精度、应用部署是否跨时区、写入频率高低——来挑选最合适的方案吧!避开 np.datetime64('now') 在这个特定需求下的陷阱,就能顺利搞定时间戳问题了。