普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月21日首页

工具指南7-Unix时间戳转换工具

作者 GeraldChen
2026年3月21日 13:32

几乎每个开发者都遇到过这种场景:后端返回一个 1710921600,你盯着它看了三秒,不知道这是哪天哪个时间。或者反过来,需要给接口传一个时间参数,要把"2026年3月20日下午2点"转换成 Unix 时间戳,打开浏览器搜 "timestamp converter"。

这个操作的频率比你想象的高。日志排查、接口调试、数据库查询、定时任务配置——时间戳无处不在。这篇文章从原理聊起,讲清楚 Unix 时间戳的设计逻辑和常见坑点,顺便分享一些实用技巧。

Unix 时间戳的本质

Unix 时间戳(Unix Timestamp)的定义很简单:从 UTC 1970年1月1日 00:00:00 到某个时刻经过的秒数

比如 0 就是 1970-01-01T00:00:00Z,86400 是 1970-01-02T00:00:00Z(一天有 86400 秒),而你读到这篇文章时的当前时间大约是 17 开头的十位数字。

这个设计来自 Unix 操作系统。1970年之前的时间用负数表示,比如 -86400 是 1969-12-31T00:00:00Z。

为什么用时间戳而不是日期字符串

直觉上,"2026-03-20 14:00:00"1773986400 更好懂。但在系统设计中,时间戳有几个明显优势:

无歧义"2026-03-20 14:00:00" 是哪个时区的?不知道。但 1773986400 指向的是一个确定的时刻,全球一致。

易计算:两个时间戳相减就是秒数差。判断 "A 是否在 B 之后" 只需要比较大小。用日期字符串做这些操作,先得解析再计算。

存储紧凑:一个 32 位整数占 4 字节,一个 ISO 8601 日期字符串至少 19 字节。在大量数据场景下差距明显。

排序高效:整数排序远快于字符串排序。数据库对整数字段的索引效率也更高。

所以后端系统和数据库普遍使用时间戳存储时间,展示时再转换成可读格式。

秒级 vs 毫秒级:别搞混了

这是最常见的踩坑点之一。不同系统和语言使用的时间戳精度不同:

精度 位数 示例 常见场景
秒级 10位 1773986400 Unix/Linux、PHP、Python time.time()
毫秒级 13位 1773986400000 JavaScript Date.now()、Java System.currentTimeMillis()
微秒级 16位 1773986400000000 Python time.time_ns() // 1000、数据库精确记录
纳秒级 19位 1773986400000000000 Go time.Now().UnixNano()、高精度计时

实际开发中最常遇到的是秒级和毫秒级的混淆。快速判断方法:数一下位数。10位是秒,13位是毫秒。

一个典型的 bug 场景:前端用 Date.now() 拿到毫秒级时间戳传给后端,后端按秒级解析,结果日期跑到了公元 58000 年。反过来也一样,后端返回秒级时间戳,前端直接传给 new Date() 不乘 1000,显示出来是 1970 年。

AnyFreeTools 的时间戳工具会自动识别输入的是秒级还是毫秒级,省去手动判断的麻烦。

时区:时间戳最容易出错的地方

时间戳本身是 UTC 时间,没有时区概念。但是当你把时间戳转换成可读日期时,时区就来了。

1773986400 这个时间戳:

  • 在 UTC 是 2026-03-20 06:00:00
  • 在北京时间 (UTC+8) 是 2026-03-20 14:00:00
  • 在纽约时间 (UTC-4, 夏令时) 是 2026-03-20 02:00:00

同一个时间戳,三个不同的"日期时间"。这不是 bug,这就是时区的本质。

常见时区问题

服务器时区不一致:前端服务器在东八区,后端服务器在 UTC,数据库在美西。不同服务拿到同一个时间戳转成本地时间后对不上,排查日志时容易困惑。

夏令时:美国每年 3 月和 11 月调整时钟。一个 cron 任务设定在"每天凌晨 2 点执行",在夏令时切换那天可能不执行(2 点被跳过了)或执行两次(2 点重复了)。

Date 对象的隐式时区转换

// 这两行代码的结果可能不同
const d1 = new Date("2026-03-20");           // 解析为 UTC 00:00
const d2 = new Date("2026-03-20T00:00:00");  // 解析为本地时区 00:00

console.log(d1.getTime() === d2.getTime());  // false(如果你不在 UTC 时区)

JavaScript 的 Date 构造函数在处理不同格式的日期字符串时,时区行为不一致。这个设计被广泛认为是 JS 时间处理中最反直觉的点之一。

最佳实践

  1. 存储和传输一律用 UTC 时间戳,展示时再转为用户所在时区
  2. API 文档明确标注时间戳精度(秒还是毫秒)
  3. 日志统一使用 UTC 时间,方便跨时区排查
  4. 避免依赖服务器本地时间,用 NTP 同步

各语言的时间戳操作

JavaScript

// 获取当前时间戳(毫秒)
const nowMs = Date.now();
const nowSec = Math.floor(Date.now() / 1000);

// 时间戳转日期
const date = new Date(1773986400 * 1000);  // 注意乘 1000
console.log(date.toISOString());           // "2026-03-20T06:00:00.000Z"
console.log(date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }));
// "2026/3/20 14:00:00"

// 日期转时间戳
const ts = new Date("2026-03-20T14:00:00+08:00").getTime() / 1000;

Python

import time
from datetime import datetime, timezone, timedelta

# 获取当前时间戳
now = int(time.time())

# 时间戳转日期
dt = datetime.fromtimestamp(1773986400, tz=timezone.utc)
print(dt.isoformat())  # "2026-03-20T06:00:00+00:00"

# 日期转时间戳
dt = datetime(2026, 3, 20, 14, 0, 0, tzinfo=timezone(timedelta(hours=8)))
ts = int(dt.timestamp())

Go

package main

import (
    "fmt"
    "time"
)

func main() {
    // 获取当前时间戳
    now := time.Now().Unix()

    // 时间戳转日期
    t := time.Unix(1773986400, 0)
    fmt.Println(t.UTC().Format(time.RFC3339))

    // 日期转时间戳
    loc, _ := time.LoadLocation("Asia/Shanghai")
    t2 := time.Date(2026, 3, 20, 14, 0, 0, 0, loc)
    fmt.Println(t2.Unix())
}

Bash

# 获取当前时间戳
date +%s

# 时间戳转日期(macOS)
date -r 1773986400

# 时间戳转日期(Linux)
date -d @1773986400

# 日期转时间戳(Linux,注意带时区)
date -d "2026-03-20T14:00:00+08:00" +%s

当然,如果只是临时查一下,没必要开终端写代码。直接打开 AnyFreeTools 时间戳转换工具,粘贴时间戳就能看到结果,支持秒级和毫秒级自动识别。

2038 问题:32 位时间的尽头

如果你用过 32 位系统,可能听说过 "Y2K38" 问题。32 位有符号整数的最大值是 2147483647,对应的时间是 2038年1月19日 03:14:07 UTC。过了这个时刻,32 位时间戳溢出,会回绕到 1901 年。

这不是假想的问题。2023 年已经有报告指出部分嵌入式设备和旧版数据库因为提前计算未来日期触发了 2038 相关的 bug。

现代系统基本已经迁移到 64 位时间戳。64 位有符号整数能表示到公元 2920 亿年后,足够用了。但如果你维护的系统中有以下情况,需要注意:

  • 数据库字段用 INT(32) 存时间戳
  • C 语言代码中用 time_t 且编译目标是 32 位
  • 嵌入式系统或 IoT 设备运行 32 位固件

检查方法很简单:试着存入 2147483648(2038 年之后的时间戳),看系统是否正常处理。

实用场景

日志排查

线上出了故障,需要定位到 "15:23:45 到 15:24:10 之间的日志"。先把这两个时间转成时间戳,然后在日志系统中按时间戳范围过滤。比手动翻日志快得多。

缓存过期

Redis 的 EXPIREAT 命令接受 Unix 时间戳:

# 设置 key 在 2026-03-21 00:00:00 UTC(北京时间 08:00)过期
redis-cli EXPIREAT mykey 1774051200

定时任务

系统的 cron 任务可能需要根据时间戳计算下次执行时间。比如"每 7 天执行一次",可以用上次执行的时间戳加上 604800(7 天的秒数)。

JWT 过期时间

JWT 的 exp(过期时间)和 iat(签发时间)字段都是 Unix 时间戳(秒级)。调试 JWT 时经常需要把这些字段转成可读时间来确认 token 是否过期。可以配合 JWT 解码工具 一起使用。

数据库时间查询

-- 查询最近 24 小时的订单(假设 created_at 存的是秒级时间戳)
SELECT * FROM orders
WHERE created_at > UNIX_TIMESTAMP() - 86400;

-- MySQL: 时间戳转日期
SELECT FROM_UNIXTIME(1773986400);

-- PostgreSQL: 时间戳转日期
SELECT to_timestamp(1773986400);

在线工具 vs 命令行

命令行的 date 命令可以做时间戳转换,但不同操作系统的语法不一样(macOS 用 -r,Linux 用 -d @),而且不支持毫秒级时间戳的直接转换。

在线工具的优势在于:

  • 零记忆成本:不用记命令语法
  • 可视化:同时显示多个时区的对应时间
  • 自动识别精度:粘贴 10 位或 13 位数字,自动判断是秒还是毫秒
  • 双向转换:时间戳 → 日期、日期 → 时间戳,一个页面搞定

AnyFreeTools 的时间戳工具还会显示当前时间的实时时间戳(每秒更新),在需要 "获取当前时间戳" 的场景下直接复制就行。

小结

时间戳看起来简单,但时区、精度、溢出这些细节处处是坑。核心原则就三条:

  1. 存储传输用 UTC 时间戳,展示再转时区
  2. 明确精度(秒 vs 毫秒),接口文档写清楚
  3. 注意 32 位限制,老系统该升级就升级

日常开发中,时间戳转换是高频低门槛的操作,没必要每次都写代码。遇到需要快速查看或转换的场景,用 在线工具更高效。

本系列其他文章


原文链接chenguangliang.com/posts/blog0…

❌
❌