日期时间格式速查表 - strftime/ISO 8601/时间戳大全
日期 bug 多半是时区 bug——本机看着对的 14:00,换台服务器就差出 8 小时。本表按 strftime 格式符、ISO 8601、时区与偏移、各语言格式化方法、时间戳转换组织,把每个 token 对应到什么输出讲清,并点出 UTC 与本地时区互转的常见坑。写日志格式、定义接口时间戳、解析 ISO 串时常来对照。读完能选到跨服务器、跨端往返一致的日期写法。
典型使用场景
面向所有要在代码里正确表达「时间」的开发者。本表覆盖四类写法:strftime 占位符(%Y/%m/%d 等)、ISO 8601 系列(带 Z、带偏移、带毫秒、周日期、序数日、持续时间)、各语言的标准库格式化写法,以及 Unix 时间戳的取用与互转。真正的难点从来不是「怎么打印」,而是时区语义:同一时刻在 UTC 和本地时区是两个字符串,America/New_York 这类 IANA 标识还含夏令时。表里的 date -d @时间戳、Intl.DateTimeFormat、now + timedelta 这几条能覆盖绝大多数格式化与换算需求。
strftime 格式符 13
%Y%y%m%d%H%I%M%S%p%A%B%Z%sISO 8601 标准 7
2024-01-152024-01-15T10:30:00Z2024-01-15T10:30:00+08:002024-01-15T10:30:00.123Z2024-W03-12024-015PT1H30M时区与偏移 6
UTCGMT+08:00Asia/ShanghaiAmerica/New_YorkDST各语言日期格式化 7
new Date().toISOString()datetime.now().strftime("%Y-%m-%d")date("+%Y-%m-%d %H:%M:%S")SimpleDateFormat("yyyy-MM-dd")time.Now().Format("2006-01-02")chrono::Local::now()Intl.DateTimeFormat().format()时间戳转换 6
Date.now()int(time.time())date +%snew Date(1705312200 * 1000)datetime.fromtimestamp(1705312200)date -d @1705312200日期运算 6
now + timedelta(days=7)new Date(Date.now() + 86400000)date -d "+1 days"d1 > d2(d2 - d1).daysdayjs(d1).isAfter(d2)常见日期格式 5
2024-01-15 10:30:00Mon, 15 Jan 2024 10:30:00 GMT2024/01/15202401151705312200参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
%Y / %m / %d / %H:%M:%S | strftime 占位符拼出人类可读日期,是 shell 与 C 系库的标准写法 | date "+%Y-%m-%d %H:%M:%S" |
ISO 8601 带 Z 或偏移 | 机器间传输与存储用带时区信息的 ISO 字符串,跨时区不失真 | 2024-01-15T10:30:00Z |
Unix 时间戳 | 秒级整数,唯一表示一个绝对时刻,做差值/排序最省事 | int(time.time()) |
时区标识 | 把本地时间与绝对时刻挂钩;IANA 标识自带夏令时规则 | America/New_York |
语言库格式化 | 各语言标准库按同一份时间数据输出不同格式,避免手写格式化 | Intl.DateTimeFormat().format() |
日期算术与比较 | 加减天数与比较先后,先把两边转成同一类型再比 | now + timedelta(days=7) |
易错点与避坑指南
现象同一份日志在服务器和浏览器显示的时间差了几个小时。
原因一边按 UTC 解析、一边按本地时区解析;%Y-%m-%d 这类无时区字符串被当成本地时间。
处置存储统一存 UTC(2024-01-15T10:30:00Z),只在展示层用 toISOString() 或指定 IANA 时区转换。
现象写 "%Y-%m-%d" 生成的时间串被后端解析后日期整体偏移。
原因%Y-%m-%d 是 strftime 写法,ISO 8601 用的是 YYYY-MM-DD;部分后端把前者当字符串硬解析。
处置接口层一律输出 ISO 8601 的大写 T 分隔形式,格式化交给前端或专门的日期库。
现象每年 3 月和 11 月,同一时刻的时间戳对不上预期。
原因时区含夏令时(DST),偏移量随日期变化,固定加 8 小时的做法在换季时会错一小时。
处置用 Asia/Shanghai、America/New_York 这类 IANA 标识交给时区库换算,不要手算偏移。
现象用「加 24 小时」实现「加一天」,夏令时当天结果差一小时。
原因夏令时切换日当天只有 23 或 25 小时,固定毫秒加法与日历语义不一致。
处置按日历语义加一天:Python 用 now + timedelta(days=1),JS 用 new Date(Date.now() + 86400000)。
排障路径
1把 Unix 时间戳转成可读日期确认时区
date -d @1705312200输出带本机时区的时间,用它和接口返回的字符串逐字段比对。
2确认同一时刻的 UTC 与本地表示
date -u "+%Y-%m-%dT%H:%M:%SZ" && date "+%Y-%m-%dT%H:%M:%S%z"两条命令并排执行,差值就是本机时区偏移。
3在 JS 里按指定时区输出日期
new Intl.DateTimeFormat("en-US",{timeZone:"Asia/Shanghai"}).format(new Date())时区名必须是合法 IANA 标识,写错会直接抛 RangeError。
命令示例
把 Unix 时间戳转成 UTC 的可读日期
date -u -d @1705312200 '+%Y-%m-%d %H:%M:%S UTC'
date -u -d @1705312200 '+%Y-%m-%dT%H:%M:%SZ'加 -u 强制按 UTC 解释,否则输出会跟着机器的本地时区变化,排查时区问题时就看不出差异了。
输出
2024-01-15 09:50:00 UTC 2024-01-15T09:50:00Z
同一时刻在三个时区的 ISO 表示
python -c "
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
d = datetime.fromtimestamp(1705312200, tz=timezone.utc)
print('utc ', d.isoformat())
print('shanghai ', d.astimezone(ZoneInfo('Asia/Shanghai')).isoformat())
print('new york ', d.astimezone(ZoneInfo('America/New_York')).isoformat())
"三个串指向同一瞬间,差别只在偏移量。America/New_York 这次显示 -05:00,是因为 1 月处于标准时间而非夏令时。
输出
utc 2024-01-15T09:50:00+00:00 shanghai 2024-01-15T17:50:00+08:00 new york 2024-01-15T04:50:00-05:00
在 JS 里按指定时区输出日期
node -e "
const ms = 1705312200000, d = new Date(ms);
console.log(d.toISOString());
console.log(new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Shanghai', dateStyle: 'long', timeStyle: 'short' }).format(d));
console.log(new Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', dateStyle: 'long', timeStyle: 'short' }).format(d));
"toISOString() 永远是 UTC,而 Intl.DateTimeFormat 按 timeZone 选项渲染。两者相差 8 小时,恰好是 Asia/Shanghai 的偏移。
输出
2024-01-15T09:50:00.000Z January 15, 2024 at 5:50 PM January 15, 2024 at 4:50 AM
算日期差值与加一天
node -e "
const d1 = new Date('2024-01-15T00:00:00Z');
const d2 = new Date('2024-03-15T00:00:00Z');
console.log('days', (d2 - d1) / 86400000);
const d = new Date('2024-01-15T10:30:00Z');
console.log('plus1day', new Date(d.getTime() + 86400000).toISOString());
"先转成毫秒数再相减得到的是时长意义上的天数;要按日历加减,用 Python 的 timedelta(days=n) 才是日历语义。
输出
days 60 plus1day 2024-01-16T10:30:00.000Z
常见坑与注意事项
- 秒级与毫秒级时间戳不要混用:Date.now() 是 13 位,Unix 时间戳是 10 位,差三个数量级会让日期整体偏移。
- 存储与传输统一用带时区的 ISO 8601(带 Z 或偏移),只在展示层转本地时区,否则服务器与浏览器显示必然不一致。
- 不要用「加 24 小时」代替「加一天」:夏令时切换当天实际是 23 或 25 小时,日历语义要用日期库的天数运算。
- %Y-%m-%d 只能用于显示。它没有时区与时间部分,传给按 UTC 解析的服务很容易整体偏移一天。
- IANA 标识(如 America/New_York)自带夏令时规则,不能用固定偏移量代替,否则每年换季时间都会错一小时。
提示
- ISO 8601 是国际标准格式,推荐在 API 和数据库中统一使用。
- Go 的参考时间是 2006-01-02 15:04:05(Mon Jan 2 15:04:05 MST 2006)。
- Unix 时间戳是秒数(10 位),JavaScript 的 Date.now() 是毫秒数(13 位)。
- 处理时区问题时,建议统一用 UTC 存储,显示时再转换为本地时区。
- Java 的 SimpleDateFormat 线程不安全,多线程环境推荐用 DateTimeFormatter。
常见问题
为什么 Date.now() 是 13 位而 Unix 时间戳是 10 位?
因为单位不同。Unix 时间戳以秒为单位,1705312200 就是秒;JavaScript 的 Date.now() 返回毫秒,所以同一个时刻是 1705312200000。两者相差正好三个数量级,混用会让时间落在 1970 年前后或几万年之后,表现为日期整体偏移或直接超出可表示范围。JS 里 Date 构造器接受毫秒,所以从秒级时间戳还原必须先乘 1000。
同一个时刻为什么在服务器和浏览器显示的时间不一样?
因为「本地时间」不是一个绝对时刻。2024-01-15T10:30:00+08:00 与 2024-01-15T02:30:00Z 指向同一瞬间,但前者按各自本地时区渲染就得到不同字符串。要消除差异,存储与传输统一用 UTC(带 Z 或 +00:00),只在最靠近人的那一层转成本地时区。ISO 8601 的带偏移形式(如 +08:00)既保留了绝对时刻,也保留了原始的偏移意图,比裸本地时间更安全。
用「加 24 小时」实现「加一天」,错在哪里?
因为一天不一定是 86400 秒。夏令时切换那天,本地时间的 00:00 到次日 00:00 只有 23 小时(春季前移)或 25 小时(秋季后移),此时用毫秒数相加得到的时间在本地时区下显示会差一小时。要表达日历意义上的「加一天」,应当用日期库的天数运算(Python 的 timedelta(days=1) 就是日历语义),或先把日期部分解析出来再加一天,而不是做时长加法。
%Y-%m-%d 这种写法为什么容易被后端解析偏掉?
因为它没有时区与时间部分。写成 %Y-%m-%d 得到的是「某个时区的某一天」,一旦传给按 UTC 解析的服务,就可能整体偏移一天——东八区生成的 2024-01-15 被当作 UTC 午夜解析,落到美国的用户那里就成了 1 月 14 日。稳妥做法是接口层一律用带时区的完整 ISO 8601 串;只有展示给人看的短日期才用 %Y-%m-%d,并且明确它只是显示格式,不能当作数据存储。
序数日(2024-015)和周日期(2024-W03-1)该怎么用?
两者都是 ISO 8601 里的日历表示,用于需要「一年中的第几天」或「一年中的第几周」的业务。周日期由 ISO week-numbering 规则算出,星期一是每周第一天,含 1 月 4 日的那一周是第 1 周,所以跨年时周数会跳变;序数日则把一年摊成 1 到 366 的连续编号。做报表按周汇总时用周日期最省事,而按天累计的场景用序数日更容易与数组下标对齐。两者都不适合人眼阅读,展示层仍应换成 YYYY-MM-DD。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。