Python中time与datetime获取毫秒时间的区别
深入解析Python中time模块与datetime模块获取毫秒时间的本质区别,涵盖底层数据形态、高精度整型与浮点机制、操作步骤、性能开销及技术选型指南。
在 Python 中获取毫秒时间,time 模块与 datetime 模块的核心差异在于数据心智模型、底层表示形态与处理目的。time 模块直接对接操作系统底层时钟,以纯数值的 Unix 纪元时间戳(1970年1月1日以来的秒数或纳秒数)为中心,从 Python 3.7 开始提供 time.time_ns() // 1_000_000,能够零浮点误差、纳秒级开销直接产出 13 位整型毫秒戳;而 datetime 模块围绕公历日历构建(年、月、日、时、分、秒、微秒),其原生最小字段仅到微秒(microsecond,0 至 999999),不存在独立的毫秒属性,必须通过整除截断或格式化配合,主要服务于跨时区业务、人类可读日志以及日期跨度运算。
一、数据形态与底层架构的本质区别
很多初学者在编写日志或打点代码时,容易随意混用 time.time() * 1000 与 datetime.now()。虽然二者最终都能导出包含毫秒的表达,但在底层对象形态和内存开销上存在本质差别。
time 模块是面向系统的轻量级封装。调用 time.time() 或 time.time_ns() 时,Python 解释器在 C 语言层直接发起 clock_gettime(CLOCK_REALTIME) 系统调用。它不构建复杂的 Python 业务对象,只返回一个双精度浮点数(秒)或一个长整型数值(纳秒)。这种纯数值模型非常紧凑,非常适合需要极高性能的网络通信序列化或全局唯一分布式 ID(如雪花算法 Snowflake)的毫秒生成。
相比之下,datetime 模块构建的是富信息对象(PyDateTime_DateTime)。每次执行 datetime.now(),解释器不仅要向系统询问当前时间戳,还要根据本地时区规则(或用户指定的时区),将其分解折算为对应的年份、月份、日期、小时、分钟、秒以及微秒。对象创建后包含丰富的日历验证与属性访问接口。因此,datetime 原生并非为了提供“时间戳计数器”,而是为了完整呈现人类日常理解的日历时刻。
二、获取毫秒时间的具体实现步骤
掌握两者区别后,我们在实际编码中如何正确获取毫秒?以下列举标准写法与容易踩坑的旧式写法。
1. time 模块获取毫秒时间
在 Python 3.7 之前,主流做法是使用 time.time() * 1000 后转换为整数;而在现代 Python(3.7 及以上版本)中,推荐使用 time.time_ns() // 1_000_000:
import time
# 方式 A(推荐,Python 3.7+):整型纳秒整除,无精度丢失
timestamp_ms = time.time_ns() // 1_000_000
# 方式 B(传统方式):浮点秒数乘 1000 后取整
legacy_ms = int(time.time() * 1000)
print("time_ns 结果:", timestamp_ms)
print("time.time 结果:", legacy_ms)
示例输出:time.time 结果: 1740801234567
为什么更推荐 time.time_ns()?因为 time.time() 返回的是 64 位双精度浮点数(IEEE 754 标准)。浮点数尾数精度仅有 53 位二进制位(等效约 15 至 17 位有效十进制数字)。当前 Unix 秒戳已经达到 10 位整数,留给小数部分的精度空间有限。在执行 * 1000 的乘法时,极小概率会因浮点舍入产生尾数晃动;而整型纳秒除法是纯数学整数截断,稳定且安全。
2. datetime 模块获取毫秒时间
datetime.datetime 对象拥有 microsecond 属性,单位为微秒(1 毫秒 = 1000 微秒)。获取毫秒通常有两种业务诉求:一种是获取当前时刻在当前秒内的毫秒数(0~999),另一种是生成包含毫秒的可读字符串,或者将对象转回 13 位毫秒戳:
from datetime import datetime
now = datetime.now()
# 1. 获取当前秒内的毫秒部分(0 到 999 整数)
ms_component = now.microsecond // 1000
# 2. 生成规范的带毫秒时间字符串(如日志场景常用)
# %f 格式化为 6 位微秒,切片截取前 3 位即为毫秒
formatted_str = now.strftime("%Y-%m-%d %H:%M:%S") + f".{ms_component:03d}"
# 或切片法:
formatted_slice = now.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
# 3. 将 datetime 对象转换为 13 位 Unix 毫秒时间戳
dt_timestamp_ms = int(now.timestamp() * 1000)
print("当前秒内毫秒:", ms_component)
print("格式化带毫秒字串:", formatted_str)
print("datetime 转毫秒戳:", dt_timestamp_ms)
示例输出:格式化带毫秒字串: 2026-11-01 13:37:44.567
示例输出:datetime 转毫秒戳: 1740801234567
三、精度机制与数值表示解析
深入探究两种方式的精度机制,可以发现 Python 官方标准库在演进中的设计考量。
1. 为什么 datetime 没有 millisecond 属性?
很多开发者初学时常常试图访问 dt.millisecond,结果遭遇 AttributeError。Python 的 datetime 设计哲学是将时间精度一次性规划到微秒级(Microsecond)。因为微秒是常见操作系统时钟能稳定提供的较小度量单位之一,提供微秒(0~999999)即可完全向下覆盖毫秒(通过 // 1000 即可无损得到毫秒)。如果额外维护一个 millisecond 字段,不仅增加内部 C 结构体冗余,还会引起属性同步问题。
2. 浮点精度陷阱与 Python 3.7+ 的演进
在 Python 3.7 引入 PEP 564 之前,所有高精度时钟(如 time.time(), time.perf_counter())都只能返回 Python float。随着硬件与网络向微秒甚至亚微秒推进,浮点数的 53 位精度限制成为了高精度指标采集的瓶颈。PEP 564 因此在 time 模块中引入了 6 个返回整型纳秒的新函数:time_ns()、perf_counter_ns()、monotonic_ns() 等。这是 time 模块在纳秒整型层面的核心优势,而 datetime 至今未支持纳秒(内部依然保持微秒)。
time.perf_counter() 和 time.perf_counter_ns() 拥有极高的计时分辨率,但它们属于单调时钟(Monotonic Clock),其基准参考点是系统开机或进程启动时间,而不是 1970 纪元。它们用于计算两点间的代码运行耗时,绝不能用来作为当前系统日历的绝对毫秒时间戳。
四、性能开销基准与选型对比
在性能敏感的应用中(如高并发 Web 框架中间件、分布式 RPC 追踪链路、行情快照数据接收),调用时间函数的开销是不可忽略的因素。
为了直观展现两者开销,以典型现代 CPython 运行环境进行单次调用测试:
time.time_ns() // 1_000_000:单次耗时约 35 ~ 50 纳秒。属于极轻量级的内核调用与整数算术。time.time():单次耗时约 30 ~ 45 纳秒。datetime.now():单次耗时约 250 ~ 380 纳秒。耗时约为time_ns()的 6 至 8 倍,主要消耗在创建datetime实例对象、日历日期拆解及属性封装上。datetime.now().strftime(...):若进一步涉及字符串格式化输出,单次耗时通常跃升至 1.5 ~ 2.5 微秒(即 1500~2500 纳秒)。
由此可见,如果你的系统每秒需要生成数十万次毫秒标记(例如链路跟踪 TraceId 或消息队列有序排队),直接采用 time.time_ns() // 1_000_000 能够节省大量 CPU 周期和内存垃圾回收(GC)压力。
五、工程实践常见问题与注意事项
1. 时钟回拨(NTP 同步)对毫秒时间的影响
无论是 time.time() 还是 datetime.now(),本质都是基于系统时钟(Wall Clock)。如果服务器运行了 NTP(网络时间协议)服务且检测到时钟漂移,系统时钟可能会发生微小调整甚至回拨。这意味着连续两次获取毫秒戳,后一次获取的值在极罕见情况下可能小于前一次。如果你的业务依赖严格单调递增的毫秒值,应在应用层维护一个基准增量,或在计时场景使用 time.monotonic()。
2. 时区(Timezone)感知与 UTC 转换
time.time_ns() // 1_000_000 返回的 Unix 毫秒时间戳是绝对时间(UTC 纪元毫秒),在地球任何时区运行所得到的值完全一致。然而 datetime.now() 默认返回的是不带时区信息的本地时间(Naive Datetime)。若要获得精确的 UTC 毫秒时间,推荐使用显式时区指定:datetime.now(timezone.utc),避免跨地域服务器部署时出现时区偏差。
3. 格式化输出毫秒的标准写法
许多开发者尝试用 strftime("%Y-%m-%d %H:%M:%S.%3f") 输出毫秒,但这在标准库中是不支持的语法(%3f 会被直接报错或原样输出)。标准实践是使用 strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] 切掉后三位微秒,或者通过 f-string 配合 dt.microsecond // 1000 手动补零格式化:f"{dt.strftime('%Y-%m-%d %H:%M:%S')}.{dt.microsecond // 1000:03d}",既清晰又高效。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









