日期时间基础:Timestamp 与 to_datetime
本教程共 54 篇 · 第 44 篇 · 更新于 2026-08-11 · 约 8 分钟阅读
本节目标:认识 pandas 的时间类型 Timestamp,学会用 to_datetime 把各种格式的日期字符串变成真正的时间,并掌握格式化输出和 Period 时间段。
为什么字符串不够用
日期数据最常以字符串形式出现:“2024-01-15”、“2024/1/15”、“20240115”……看起来能读,但字符串有几个硬伤:
- 不能比较。字符串比较按字典序,“2024-1-2” 和 “2024-1-10”,字典序里 “2024-1-2” 更大,但按日期它更小。
- 不能运算。“2024-01-15” 加 3 天等于什么?字符串没有答案。
- 格式混乱。同一天有十几种写法,程序没法统一处理。
所以 pandas 提供了专门的时间类型。拿到数据的第一步,就是尽早把日期字符串转成真正的时间。
pandas 的四个时间概念
pandas 把”时间”拆成四个概念,先混个脸熟:
| 概念 | 标量类 | 数组类 | 一句话 |
|---|---|---|---|
| 时间点 | Timestamp | DatetimeIndex | 某个具体时刻,如 2024-01-15 08:30 |
| 时间差 | Timedelta | TimedeltaIndex | 一段时长,如 3 天 2 小时 |
| 时间段 | Period | PeriodIndex | 一段区间,如 2024 年 1 月 |
| 日期偏移 | DateOffset | 无 | 日历运算,如”下个工作日” |
本节先讲时间点 Timestamp 和它的批量版本;时间差、偏移留给后面章节。
Timestamp:单个时间点
Timestamp 是 pandas 的标量时间类型,表示一个精确的时间点。创建方式很灵活:
import pandas as pd
# 字符串创建
print(pd.Timestamp("2024-01-15"))
# 2024-01-15 00:00:00
# 位置参数:年、月、日
print(pd.Timestamp(2024, 1, 15))
# 带时间
print(pd.Timestamp("2024-01-15 08:30:00"))
# 从 Python 原生 datetime 转换
import datetime
print(pd.Timestamp(datetime.datetime(2024, 1, 15)))
拿到 Timestamp 之后,年、月、日、星期几这些信息都能直接取:
import pandas as pd
t = pd.Timestamp("2024-01-15 08:30:00")
print(t.year) # 2024
print(t.month) # 1
print(t.day) # 15
print(t.hour) # 8
print(t.dayofweek) # 0,周一(0=周一,6=周日)
print(t.day_name()) # Monday
print(t.quarter) # 1,第一季度
print(t.is_month_start) # False,不是月初
底层上,Timestamp 由 NumPy 的 datetime64 类型支撑,一组 Timestamp 存进 Series 后,dtype 显示为 datetime64[us]。us 是微秒(microsecond)。pandas 3.0 起解析普通字符串默认用微秒精度,只有字符串自带纳秒精度时才会用 ns。
时间序列里没有”缺失日期”的概念,缺失的时间统一用 NaT(Not a Time)表示,作用和 NaN 一样。
批量转换:to_datetime
实际数据里日期是成列的,不会一个一个转。pd.to_datetime 是批量转换的主力,输入什么类型,输出什么类型:
import pandas as pd
# 列表 -> DatetimeIndex(时间索引)
print(pd.to_datetime(["2024-01-01", "2024-01-02", "2024-01-03"]))
# DatetimeIndex(['2024-01-01', '2024-01-02', '2024-01-03'], dtype='datetime64[us]', freq=None)
# 单个字符串 -> Timestamp
print(pd.to_datetime("2024-01-01"))
# 2024-01-01 00:00:00
# Series -> Series(列转时间类型)
s = pd.Series(["2024-01-01", "2024-01-02"])
print(pd.to_datetime(s))
# 0 2024-01-01
# 1 2024-01-02
# dtype: datetime64[us]
读 CSV 时也可以直接用 parse_dates 参数让 pandas 在读入阶段就解析日期列(§11),效果和读完后 to_datetime 一样。
指定格式:format
大部分字符串 pandas 能自动识别。但格式太刁钻,或者想加快解析速度,可以手动指定 format。格式代码和 Python 的 strftime 一致:%Y 四位年、%m 两位月、%d 两位日、%H 小时、%M 分钟。
import pandas as pd
# "日月年"的顺序,必须显式指定格式
print(pd.to_datetime("12-11-2010 10:00", format="%d-%m-%Y %H:%M"))
# 2010-11-12 10:00:00
这里 “12” 是日、“11” 是月,不指定格式会被当成”月-日-年”解析成 2010-12-11,语义完全不同。
Warningpandas 2.0 起,一个列表里混着多种格式的字符串,默认会直接报错,不再自动逐个猜。要么保证格式统一,要么显式传
format="mixed":pd.to_datetime(["2024-01-01", "2024/01/02"], format="mixed")。格式统一时指定 format 还能明显提速。
解析失败怎么办:errors
数据里总有脏值。errors="coerce" 把解析不了的值变成 NaT,而不是让整个转换崩掉:
import pandas as pd
print(pd.to_datetime(["2024-01-01", "invalid", "2024-01-03"], errors="coerce"))
# DatetimeIndex(['2024-01-01', 'NaT', '2024-01-03'], dtype='datetime64[us]', freq=None)
时间戳(epoch)转换
有些系统用整数时间戳存时间,比如 Unix 时间戳:从 1970-01-01 起算的秒数。用 unit 告诉 pandas 单位是什么:
import pandas as pd
# unit="s":按秒解释
print(pd.to_datetime([1349720105, 1349806505], unit="s"))
# DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05'], dtype='datetime64[s]', freq=None)
# unit="ms":按毫秒解释
print(pd.to_datetime([1349720105100, 1349806505100], unit="ms"))
反过来,把时间转回时间戳:(索引 - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s"),或者用 astype(np.int64)。
从多个列组装日期
日期被拆成了”年、月、日”三列?不用拼字符串,直接把 DataFrame 传给 to_datetime 就行:
import pandas as pd
df = pd.DataFrame({"year": [2015, 2016], "month": [2, 3], "day": [4, 5]})
print(pd.to_datetime(df))
# 0 2015-02-04
# 1 2016-03-05
# dtype: datetime64[us]
列名必须是 year、month、day 这种标准名字,可选 hour、minute、second。
格式化输出:strftime
时间转成字符串用 strftime,格式代码和前面 format 一样。对单个 Timestamp 直接调,对一列时间用 .dt.strftime:
import pandas as pd
t = pd.Timestamp("2024-01-15")
print(t.strftime("%Y年%m月%d日"))
# 2024年01月15日
print(t.strftime("%A"))
# Monday
# 一列时间批量格式化
s = pd.Series(pd.date_range("2024-01-15", periods=3, freq="D"))
print(s.dt.strftime("%Y-%m-%d"))
# 0 2024-01-15
# 1 2024-01-16
# 2 2024-01-17
# dtype: str
.dt 是 Series 的日期访问器,后面跟 year、month、day_name()、strftime 等方法,作用是把”每个元素的时间属性”批量取出来。日期的星期名、季度、是否月初等,也都在 .dt 上。
日期的比较与排序
转成时间类型后,比较和排序也顺理成章了。这正是开头说”字符串不够用”的根源:
import pandas as pd
s = pd.Series(pd.to_datetime(["2024-01-10", "2024-01-02", "2024-01-05"]))
print(s < pd.Timestamp("2024-01-06"))
# 0 False
# 1 True
# 2 True
# dtype: bool
print(s.sort_values())
# 1 2024-01-02
# 2 2024-01-05
# 0 2024-01-10
# dtype: datetime64[us]
排序、去重、比较大小,时间列全都支持。配合布尔索引(§20),“筛选出 2024 年 1 月 6 日之前的记录”就是一行代码的事。
Period:表示时间段
有些数据对应的不是某个时刻,而是一整个时间段。“2024 年 1 月的销售额”,时间点是 1 月 31 日 23:59:59 还是 1 月 1 日 00:00:00?都不对,它属于整个 1 月。这种数据用 Period(时间段)更合适:
import pandas as pd
p = pd.Period("2024-01") # 不写 freq,默认按字符串推断为月
print(p)
# 2024-01
print(p + 1) # 时间段可以平移
# 2024-02
# 指定频率:按天
print(pd.Period("2024-01-15", freq="D"))
# 2024-01-15
# 月时间段转成时间段内第一天
print(p.asfreq("D", "start"))
# 2024-01-01
Period 最大的好处是”能运算、能比较、天然对齐”,比如 pd.period_range("2024-01", periods=4, freq="M") 生成一串连续月份。把时间戳列变成 Period,常用 .dt.to_period:
import pandas as pd
s = pd.Series(pd.date_range("2024-01-15", periods=3, freq="D"))
print(s.dt.to_period("M"))
# 0 2024-01
# 1 2024-01
# 2 2024-01
# dtype: period[M]
三个不同的日子都归到 2024-01 这个时间段,后面做”按月份分组统计”(§46 重采样)时非常顺手。
Tip拿不准用 Timestamp 还是 Period:数据是”某时刻发生了什么”用 Timestamp;数据是”某段时间内累计的结果”(月报、季报)用 Period。
小结
这一节把时间类型的地基打好了:Timestamp 表示时刻,to_datetime 负责批量转换,strftime 负责格式化,Period 表示时间段。下一节把时间放到索引上,讲讲 DatetimeIndex 和 date_range 生成日期范围。