资讯动态

Python时间处理详解:时间戳、time与datetime模块核心用法与避坑指南

发布时间:2026/10/15 12:06:03 来源:尧图企业网站定制
日志里偶尔会蹦出一串像1672560000这样的数字或者收到前端传过来的一个字符串要转成 Python 里的时间对象。这个场景但凡写过一点业务代码的开发者都绕不开。很多人一开始对着time模块和datetime模块发懵两个模块都能拿当前时间都能格式化到底用哪个怎么转来转去就“差8小时”时间戳又是个什么鬼这篇内容我把 Python 里跟时间相关的底层概念和两个核心模块一次性讲透从时间戳的由来到time模块的底层操作再到datetime模块的对象化处理配合我实际项目中常写的几个转换模板以及踩过的一些坑。目标是让你看完之后再遇到时间处理需求时能直接动手而不是打开搜索引擎翻半天。1. 时间戳到底是什么先搞懂时间的底层表示1.1 时间戳的定义与“1970年起点”的故事时间戳Timestamp在计算机世界里是一个浮点数或整数表示从某个约定好的起点开始到当前时刻所经过的秒数。这个起点在 Unix 系统里是1970-01-01 00:00:00 UTC也就是常说的 Unix Epoch。为什么选1970年这不是拍脑袋定的跟 Unix 操作系统诞生年代直接相关当时设计者为了方便表示时间直接取了系统运行时的某个时刻做起点慢慢地这个约定被广泛沿用下来。你不需要深入研究历史只需要记住一个关键点时间戳永远是相对 UTC 时间计算的它跟你在哪个国家、用的哪个时区没有任何关系。所以1672560000这个数字在世界上任何一个角落、任何一台服务器上它的含义都是完全一致的都指向同一个瞬间。这就是时间戳最大的价值它是一种无歧义的时间表达方式适合存储、传输和排序比较。1.2 为什么业务代码里到处都是时间戳人类习惯看“2024年1月1日 12:30:00”但计算机更擅长处理数值。用时间戳有几个实打实的好处。第一存储紧凑。一个整数占4到8个字节而一个格式化字符串可能要占20多个字节。在日志系统、埋点数据、数据库索引这些场景里用时间戳能省下不少空间排序效率也更高。第二比较方便。想知道 A 事件和 B 事件哪个先发生时间戳直接比大小就行不用考虑“月”和“日”谁优先的问题。想知道两个时间之间隔了多久直接做减法得到的就是精确的秒数。第三与时区解耦。时间戳不关心你在北京还是纽约。如果业务系统直接用字符串存本地时间一旦服务器时区配置变了或者用户跨时区访问整个数据就全乱了。而存时间戳展示的时候再按需转成对应时区永远不会有这种问题。1.3 时间戳的精度秒、毫秒与微秒很多人第一次接触时间戳会遇到一个经典困惑为什么有些时间戳是10位数有些是13位数10位数字的时间戳是以秒为单位13位则是以毫秒为单位。比如1672560000是秒级1672560000000就是毫秒级。在 Python 里time.time()返回的是浮点数理论上可以精确到微秒级别比如1672560000.123456。但要注意实际精度取决于操作系统和硬件。在 Windows 上time.time()的精度往往只有毫秒级在 Linux 上通常是微秒级。如果你需要更高精度或者需要保证计时不受系统时间调整影响那就得用后面会提到的time.perf_counter()和time.monotonic()。2. time模块贴近系统的时间处理工具2.1 四个核心函数time.time、localtime、gmtime、mktimetime模块是 Python 里最底层的时间接口它直接跟操作系统的时间打交道。日常用得最多的四个函数我挨个过一遍。time.time()返回当前时间戳等价于 Unix 系统调用gettimeofday的简化封装。它是获取当前时间最直接的方式返回浮点数。time.localtime([timestamp])把一个时间戳转换成你当前系统的本地时间。如果不传参数默认使用当前时间。它返回一个struct_time对象这个对象像是“命名元组”可以通过属性名访问年、月、日、时、分、秒等字段。注意了localtime会受到系统时区设置的影响同一个时间戳在不同的时区机器上得到的小时数是不一样的。time.gmtime([timestamp])跟localtime用法完全相同但返回的是 UTC 时间也就是协调世界时不受本机时区影响。平时调试时推荐用gmtime来确认时间戳的原始含义。time.mktime(struct_time)是localtime的逆操作它把一个struct_time本地时间再转回时间戳。这里有个容易踩的坑mktime认为你传进去的是本地时间会自动做一次时区换算。如果你本来手里是一个 UTC 的struct_time直接丢给mktime结果就不是你想要的。import time now_ts time.time() print(f当前时间戳: {now_ts}) local_struct time.localtime(now_ts) utc_struct time.gmtime(now_ts) print(f本地时间: {local_struct.tm_year}-{local_struct.tm_mon}-{local_struct.tm_mday} f{local_struct.tm_hour}:{local_struct.tm_min}:{local_struct.tm_sec}) print(fUTC时间: {utc_struct.tm_year}-{utc_struct.tm_mon}-{utc_struct.tm_mday} f{utc_struct.tm_hour}:{utc_struct.tm_min}:{utc_struct.tm_sec}) # 从本地时间的 struct_time 转回时间戳 back_ts time.mktime(local_struct) print(f转回时间戳: {back_ts})2.2 格式化输出strftime 和 strptimetime.strftime(format, struct_time)是把结构化时间对象转成可读字符串。time.strptime(string, format)则是把字符串解析成struct_time对象。这两个函数用得极其频繁我把最常用的格式符列成一张表。格式符含义示例%Y四位年份2024%y两位年份24%m两位月份01%d两位日期05%H24小时制小时14%I12小时制小时02%M分钟30%S秒45%f微秒123456%zUTC偏移0800%Z时区名称CST%A星期全名Monday%a星期缩写Mon%B月份全名January%b月份缩写Jan%j一年中的第几天005%w星期数字0为周日3实际写的时候我建议字符串里尽量别混入中文因为不同平台的编码处理容易出幺蛾子。比如%Y-%m-%d %H:%M:%S这种格式是跨平台最稳的写法。2.3 不要用 time 模块做记时perf_counter 才是正确选择time.sleep(seconds)用来阻塞当前线程这个没什么好说的传几秒就是几秒。但如果你需要用 Python 来做性能测试、统计某段代码跑了多久千万别直接用time.time()来做减法。为什么不建议因为time.time()返回的是“墙上时钟时间”它有可能被系统时间同步、用户手动改时间、甚至 NTP 校时影响。你在测试代码性能的过程中系统时间突然往前跳了1秒你算出来的耗时就是负数这种问题排查起来非常痛苦。正确做法是用time.perf_counter()它专门为性能测量设计精度高而且是单调递增的不会因为系统时间调整而跳变。还有time.monotonic()同样是单调时钟适合计算时间间隔。两者的区别在于perf_counter包含休眠时间而monotonic在不同平台上对休眠的处理有差异具体到你的场景记耗时用perf_counter就对了。import time start time.perf_counter() # 模拟一段耗时操作 time.sleep(0.02) end time.perf_counter() print(f代码段耗时: {(end - start) * 1000:.2f} 毫秒)3. datetime模块更人性化的时间对象3.1 从 datetime 到 date、time对象家族速览time模块处理起来比较原始你要从struct_time里拿一个字段就得用tm_year这种冷冰冰的名字。而datetime模块提供了更符合直觉的对象日常开发的主力就是它。datetime.datetime是包含日期和时间完整信息的对象你既可以用点语法拿到.year、.month、.day、.hour、.minute、.second、.microsecond也可以直接做运算。datetime.date只关注年月日datetime.time只关注时分秒datetime.timedelta表示两个时间点之间的差值。初始化一个datetime对象很简单from datetime import datetime, date, time, timedelta # 直接构造 dt datetime(2024, 5, 1, 12, 30, 45, 123456) print(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond) # 获取当前本地时间 now datetime.now() # 获取当前 UTC 时间注意这里返回的是 naive datetime utc_now datetime.utcnow()datetime.now()返回的是本地时间的datetime对象datetime.utcnow()返回的是 UTC 时间的对象。在 Python 3.12 之前utcnow()返回的是不带时区信息的“天真时间”这里面埋了不少坑后面专门讲。3.2 timedelta让日期计算像数学一样简单有了timedelta日期加减就变成了小学算术。你想知道3天前的日期直接减。from datetime import datetime, timedelta now datetime.now() three_days_ago now - timedelta(days3) next_week now timedelta(weeks1) after_two_hours now timedelta(hours2) print(f当前: {now}) print(f3天前: {three_days_ago}) print(f下周: {next_week}) print(f2小时后: {after_two_hours}) # 两个时间点相减得到一个 timedelta 对象 delta next_week - now print(f相差秒数: {delta.total_seconds()})timedelta支持的参数包括days、seconds、microseconds、milliseconds、minutes、hours、weeks足够覆盖几乎所有相对时间计算。它内部会把所有数据统一换算成天、秒、微秒三个维度所以你可以放心混用不同的单位。3.3 时区与 UTC如何避免“差8小时”的经典事故这是整个时间处理里最容易出问题的环节。我刚工作那会儿因为这个“差8小时”问题被坑过不止一次。核心规则只有一条内部存储和传输一律用 UTC展示的时候转成本地时间。在 Python 里规范做法是使用带时区信息的datetime即timezone-aware对象。from datetime import datetime, timezone, timedelta # 获取当前 UTC 时间带时区信息推荐方式 utc_dt datetime.now(timezone.utc) print(fUTC时间: {utc_dt.isoformat()}) # 转换到北京时间UTC8 beijing_tz timezone(timedelta(hours8)) beijing_dt utc_dt.astimezone(beijing_tz) print(f北京时间: {beijing_dt.isoformat()}) # 转回时间戳 ts utc_dt.timestamp() print(f时间戳: {ts})注意datetime.now(timezone.utc)和datetime.utcnow()在语义上的区别。前者生成的是带 UTC 时区标记的对象后者生成的是不带任何时区标记的naive时间。当你调用timestamp()方法时Python 会假定 naive 对象是“本地时间”然后做转换这就可能导致你拿到的结果比预期的多了或者少了8小时。我在代码里始终坚持这么一条所有datetime对象只要一产生就立刻带上timezone.utc不做任何无时区对象的时间戳转换。这个习惯帮我挡掉了大量隐性 bug。4. 时间转换实战这些场景你会天天用到4.1 场景一日志文件里时间戳转可读时间这是出现频率最高的需求。后端日志里记的是秒级或毫秒级时间戳你拿到后想转成“2024-05-01 12:30:45”这种格式来看。import time from datetime import datetime, timezone # 秒级时间戳 ts 1672560000 dt datetime.fromtimestamp(ts) # 注意fromtimestamp默认转本地时间 print(f本地时间: {dt.strftime(%Y-%m-%d %H:%M:%S)}) # 用UTC方式转换避免本机时区干扰 dt_utc datetime.fromtimestamp(ts, tztimezone.utc) print(fUTC时间: {dt_utc.strftime(%Y-%m-%d %H:%M:%S)}) # 毫秒级时间戳先除以1000 ts_ms 1672560000123 dt_ms datetime.fromtimestamp(ts_ms / 1000) print(f毫秒时间戳转日期: {dt_ms.strftime(%Y-%m-%d %H:%M:%S.%f)})datetime.fromtimestamp()的默认行为是转换成本地时间这符合大多数展示需求。但如果你要处理的是全球用户的数据请务必传tztimezone.utc统一转成 UTC 再让前端自己处理时区。4.2 场景二前端传参字符串转 datetime 和反向输出接口开发中最常见的需求就是从请求参数里拿到2024-05-01 12:30:45这种字符串转成datetime对象做比较或计算。from datetime import datetime date_str 2024-05-01 12:30:45 fmt %Y-%m-%d %H:%M:%S # 字符串 - datetime dt datetime.strptime(date_str, fmt) print(f解析后的对象: {dt}) # datetime - 字符串 output dt.strftime(%Y/%m/%d %H:%M:%S) print(f格式化输出: {output}) # ISO 8601 风格格式化 iso_output dt.isoformat() print(fISO格式: {iso_output})这里有个细节strptime是严格匹配格式的%Y-%m-%d不会自动容忍2024/05/01这种斜杠写法。如果前端传过来的格式不统一最好先做一次规范性清洗比如把/替换成-再走解析流程。另外%f只解析微秒如果你拿到的是毫秒字符串要先补齐到6位或者直接用时间戳处理。4.3 场景三时间区间判断与定时任务很多时候你要判断“当前时间是否在某个时间段内”比如活动开售时间、优惠券有效期、接口限流窗口这些用datetime比较非常直观。from datetime import datetime, timedelta now datetime.now() start datetime(2024, 5, 1, 10, 0, 0) end datetime(2024, 5, 1, 18, 0, 0) if start now end: print(当前处于活动时间段内) else: print(当前不在活动时间段内) # 判断时间差是否超过阈值 last_trigger_at datetime(2024, 5, 1, 9, 0, 0) if now - last_trigger_at timedelta(minutes5): print(距离上次触发超过5分钟可以再次执行)定时任务里也可以用时间戳来判断“下一次执行时间”比如明天凌晨三点from datetime import datetime, timedelta, time today datetime.now().date() tomorrow_3am datetime.combine(today, time(3, 0)) timedelta(days1) wait_seconds (tomorrow_3am - datetime.now()).total_seconds() print(f距离明天凌晨3点还有 {wait_seconds:.0f} 秒)4.4 场景四默认过期时间与相对日期给缓存、验证码、临时链接设置过期时间是后端每天都在干的事。这里必须注意一个准则存绝对时间戳而不是存相对秒数。from datetime import datetime, timedelta, timezone import time # 方式1当前时间 偏移量 expire_dt datetime.now(timezone.utc) timedelta(seconds300) expire_ts expire_dt.timestamp() print(f5分钟后过期的时间戳: {expire_ts}) # 方式2直接在当前时间戳上做加法更轻量 expire_ts2 time.time() 300 # 校验是否过期 now_ts time.time() if now_ts expire_ts: print(已过期) else: print(f还剩 {expire_ts - now_ts:.0f} 秒过期)我见过不少人在数据库里存“这个验证码还差600秒过期”这种相对值然后在代码里做减法。这在小规模场景没问题一旦涉及多台服务器、服务重启、时间校准这些相对值马上就乱了。绝对时间戳是唯一保险的做法——生成的时候就算出到期时间点存这个到期时间点判断时只需要比较当前时间戳和它的大小。4.5 time 还是 datetime到底该用哪个这是新手问得最多的问题。我的习惯判断标准有三个。第一只获取当前时间戳、做性能计时、 sleep优先用time模块。它更底层开销小代码也更简洁。第二涉及日期加减、字段读写、格式解析、跨时区展示直接用datetime。它的对象模型更清晰timedelta做日期运算非常顺手。第三两个模块可以混用但不要在同一段逻辑里切来切去。比如你先用time.time()拿到时间戳之后需要格式化展示就一次性用datetime.fromtimestamp(ts)转过去之后所有操作都在datetime体系里完成。反向也一样最终要存数字时再调用.timestamp()转回来。5. 实战中的坑与排查经验5.1 坑一系统时区问题导致的“神秘8小时”现象服务器上跑得好好的脚本拿到的时间戳转成字符串后比预期多了8小时或者少了8小时。原因几乎都是系统时区配置不正确或者是代码直接用了datetime.utcnow()返回的 naive 对象然后做本地化展示。排查方法很简单先在服务器上执行date命令看系统时区再写一行代码打印time.tzname看 Python 识别到的是什么时区。我的建议是所有 Docker 镜像和部署脚本里都显式设置环境变量TZUTC让服务端统一 UTC 环境展示层交给前端处理。这样能最大限度减少时区相关的魔幻问题。5.2 坑二timestamp() 对无时区对象的本地时区假设这是最隐蔽的一个坑。假设你有一个 naive 的datetime对象比如通过strptime解析用户输入得到的它没有时区信息。你调用.timestamp()时Python 会自动假设这个时间是你的“本地时间”然后换算成时间戳。如果用户输入的是北京时间而你的服务器运行在 UTC 时区那么转换出来的时间戳就少了8小时。正确的做法是在解析用户输入后立刻给它指定时区再用timestamp()转换。from datetime import datetime, timezone, timedelta date_str 2024-05-01 12:30:45 fmt %Y-%m-%d %H:%M:%S # 错误示范假设本地时间 naive_dt datetime.strptime(date_str, fmt) wrong_ts naive_dt.timestamp() # 正确方式指定北京时间再转 beijing_tz timezone(timedelta(hours8)) aware_dt datetime.strptime(date_str, fmt).replace(tzinfobeijing_tz) right_ts aware_dt.timestamp() print(f错误时间戳: {wrong_ts}) print(f正确时间戳: {right_ts})5.3 坑三strptime 的格式匹配太严格strptime要求格式和字符串严格对应。比如%Y-%m-%d不会识别2024-5-1因为%m默认要两位数字。有的人会用正则先把字符串清洗成固定格式再交给strptime解析也有人选择直接用第三方库比如dateutil的parse函数它能智能解析很多不规则的日期格式。这里我给的建议是接口层的数据能做严格校验就做严格校验格式不规范直接返回参数错误。正则清洗也别硬上容易引入边界问题。如果确实要兼容多种格式宁可手写几个strptime分支也别用那种“什么都吃”的解析器出错了很难排查。5.4 坑四服务器时间不同步多机部署时时间戳看似没问题其实隐患很大。如果服务器没有配置 NTP 同步一台机器快了2分钟另一台慢了1分钟那么它们生成的时间戳比较起来就没有意义。我遇到过真实情况两台机器在处理同一批消息A 生成的时间戳比 B 晚了3分钟导致 B 认为消息失效直接丢弃。排查到最后发现是服务器时间漂移。解决方案很简单统一部署 NTP 定时同步代码里对“时间差过大的请求”做容错处理至少不要因为几十秒的时间差就拒绝服务。5.5 一个小排查案例展示有一次线上接到反馈后台统计报表“今日数据”一直不准跨天的数据偶尔会串到前一天。排查过程大概是这样的。先看统计逻辑发现报表按日期分组日期是用datetime.now().strftime(%Y-%m-%d)生成的。再看数据入库逻辑发现时间字段存的是datetime.utcnow()一个 naive UTC 时间。问题就出在这里datetime.now()取的是本地时间入库用的是 UTC 时间两个时间在东八区相差了8小时。凌晨0点到8点之间产生的数据入库后 UTC 日期还是前一天报表自然就把数据算到了前一天。修复方式是在入库环节统一用datetime.now(timezone.utc)展示时再转本地时区并明确所有日期分组都用同一个“可观测时间的标准”。这个案例给所有人的教训是同一个项目里必须要区分“事件发生时间”和“本地展示时间”不是所有now()都是一样的。6. 我的一点心得体会做了这么久时间处理我最大的感受是时间问题的 bug 往往不是出现在“不会写”上而是出现在“想当然”上。当你脑子里默认“当前时间就是服务器本地时间”或者默认“所有时间都是同一时区”那么跳变就会在你看不到的地方出现。建议大家在项目一开始就定义好时间处理的规范比如统一用 UTC 存储、统一用 ISO 8601 字符串传输、统一在边界层转换时区。这些约定看起来繁琐但能省下后面无数排查时间。再有就是测试用例里一定要写几组跨时区、跨日、跨月的边界数据光靠“今天跑一遍没问题”远远不够。如果你现在正被某个时间转换问题逼得头疼不妨先停下来用我上面说的“时间戳-UTC对象-本地展示”这条链路梳理一下大部分问题都能迎刃而解。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑