pandas to_datetime 使用自定义 origin 后,微秒和时分秒消失
pandas 按天偏移转换日期时,自定义起点的微秒或日内时间可能丢失。用时间差确认截断,再改为时间差加完整起点。
把一组“距起点多少天”的整数转换成日期,起点明明是 2016-01-01 00:00:00.000001,结果却都落在整点零秒。这种丢失可能发生在 pd.to_datetime(..., unit="D", origin=...) 的起点换算阶段,未必是输出格式隐藏了微秒。
在已出现该行为的 pandas 2.x 路径以及可核对的 3.0.6 实现中,自定义起点会先换成输入单位对应的整数偏移。输入单位是天,起点中不足一天的部分可能在整除时丢失。类似地,输入单位比起点精度粗,也需要检查同一条路径。
把输入单位和起点精度分开看
下面的例子中,1 表示起点之后的一天,不表示 Unix 纪元之后的一天:
import pandas as pd
origin = pd.Timestamp("2016-01-01 00:00:00.000001")
offsets = [1, 2, 3]
converted = pd.to_datetime(offsets, unit="D", origin=origin)
print(converted)
print(converted[0] - origin)
按输入含义,首项应比起点恰好多一天,尾部微秒应保留。如果首项变成 2016-01-02 00:00:00,差值就会少一微秒。用时间差判断,可以排除“只是打印时没有展示”的情况。
起点带有十小时等更大的日内偏移时,也可能看到同类损失。不要只检查结果的日期部分,更不要在发现结果是 datetime64[s] 或 datetime64[ns] 后,仅靠改 dtype 认定问题已解决:已经被截掉的起点信息不会因后续提高存储精度而恢复。
先构造时间差,再加上完整起点
当业务定义确实是固定长度时间间隔时,可以显式拆成两步:
import pandas as pd
origin = pd.Timestamp("2016-01-01 00:00:00.000001")
offsets = [1, 2, 3]
result = origin + pd.to_timedelta(offsets, unit="D")
print(result)
print(result - origin)
这里先把整数转成天数对应的 TimedeltaIndex,再与完整的 Timestamp 相加,避免把起点先压到整天。首项预期为 2016-01-02 00:00:00.000001,后两项也保留同样的微秒部分;每项与起点的差应分别为一、二、三天。
如果原输入是带业务索引的 Series,同时确认结果的索引和名称是否符合调用方要求。修改计算方法后,不应只取 .values 让标签丢失,也不要把结果转成格式化字符串来做后续时间计算。
这条替代路径适用于普通时间起点与数值间隔,不是 origin="julian" 的通用替换。还需保留原来的空值、非法输入与范围检查;新的加法同样可能因数据超出可表示范围而失败。
一天是固定时长,还是当地日历的一天
pd.to_timedelta(..., unit="D") 表达固定二十四小时。若业务要的是地区时区里“下一天同一墙上时间”,夏令时切换日可能不是二十四小时,应按日历规则选择 DateOffset 等适合的运算。不要把保留起点精度和处理时区日历混为一个修改。
也不建议手工修改 pandas 安装目录,把起点换算中的整除改成普通除法。浮点数表达大的时间偏移时仍可能引入精度误差,而且这种改动会绕过包管理与后续升级。
在替换任务入口前,保留原始数值、单位和完整起点,比较零偏移、正偏移、负偏移与业务允许的最大偏移。零偏移尤其有用:结果应等于起点本身。若之前已经把截断后的时间存入数据库或文件,重新计算时写入新结果并对比,别覆盖唯一的输入记录。


