时间戳是什么
打开数据库,你可能会看到 created_at 字段存着 1712345678 这样一串数字。这就是 Unix 时间戳——一个看上去毫无意义的整数,却是计算机世界里最通用的时间表示方式。
Unix 时间戳的定义:从 1970 年 1 月 1 日 00:00:00 UTC(协调世界时)到某一时刻所经过的秒数。
这个起点被称为 Unix 纪元(Unix Epoch),也叫"epoch time"。之所以选 1970 年,是因为 Unix 操作系统诞生于 1969-1970 年前后,这个时间点对早期开发者来说足够"现代",又方便计算。
为什么用时间戳,而不直接存日期字符串
存 "2024-04-06 10:30:00" 不香吗?实际上在系统开发中,时间戳比日期字符串有几个关键优势:
1. 没有时区歧义
"2024-04-06 10:30:00" 到底是北京时间还是纽约时间?不知道。而时间戳 1712374200 永远只代表一个确定的时刻,不依赖任何时区。
2. 计算方便
两个时间戳相减,直接得到秒数差。判断某事件是否在一小时内:now - event_time < 3600,简单明了。
3. 存储高效
一个 32 位整数只需 4 字节,而日期字符串至少需要 19 字节。
4. 跨语言、跨平台通用
所有编程语言、所有操作系统都支持 Unix 时间戳,不存在格式兼容问题。
秒 vs 毫秒:最常见的踩坑
时间戳有两种精度,这是新手最容易搞混的地方:
| 精度 | 示例值 | 位数 | 常见场景 |
|---|---|---|---|
| 秒级(s) | 1712374200 |
10位 | Unix标准、数据库、服务器日志 |
| 毫秒级(ms) | 1712374200000 |
13位 | JavaScript、前端、高精度日志 |
JavaScript 的 Date.now() 返回的是毫秒,这是很多 bug 的来源。把毫秒时间戳当秒传给后端,后端会解析出一个几万年后的时间。
快速判断方法:看位数,10位是秒,13位是毫秒。
// JavaScript 获取秒级时间戳
Math.floor(Date.now() / 1000) // 正确
Date.now() // 毫秒,注意区分
# Python 获取时间戳
import time
time.time() # 浮点数,秒级(含小数)
int(time.time()) # 整数秒
各语言获取与转换时间戳
JavaScript / Node.js
// 获取当前时间戳(秒)
const ts = Math.floor(Date.now() / 1000);
// 时间戳转日期
const date = new Date(ts * 1000);
console.log(date.toISOString()); // "2024-04-06T02:30:00.000Z"
// 日期转时间戳
const ts2 = Math.floor(new Date("2024-04-06T10:30:00+08:00").getTime() / 1000);
Python
import time
from datetime import datetime, timezone
# 获取当前时间戳(秒)
ts = int(time.time())
# 时间戳转日期(UTC)
dt_utc = datetime.fromtimestamp(ts, tz=timezone.utc)
print(dt_utc.isoformat()) # "2024-04-06T02:30:00+00:00"
# 时间戳转本地时间
dt_local = datetime.fromtimestamp(ts)
print(dt_local) # 按系统时区显示
# 日期字符串转时间戳
dt = datetime.fromisoformat("2024-04-06T10:30:00+08:00")
ts2 = int(dt.timestamp())
Java
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.ZoneId;
// 获取当前时间戳(秒)
long ts = Instant.now().getEpochSecond();
// 时间戳转日期
Instant instant = Instant.ofEpochSecond(ts);
ZonedDateTime zdt = instant.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zdt); // 2024-04-06T10:30:00+08:00[Asia/Shanghai]
// 日期转时间戳
ZonedDateTime zdt2 = ZonedDateTime.parse("2024-04-06T10:30:00+08:00");
long ts2 = zdt2.toEpochSecond();
Go
import (
"fmt"
"time"
)
// 获取当前时间戳(秒)
ts := time.Now().Unix()
// 时间戳转日期
t := time.Unix(ts, 0).UTC()
fmt.Println(t.Format(time.RFC3339)) // "2024-04-06T02:30:00Z"
// 日期转时间戳
layout := "2006-01-02T15:04:05Z07:00"
t2, _ := time.Parse(layout, "2024-04-06T10:30:00+08:00")
ts2 := t2.Unix()
PHP
// 获取当前时间戳(秒)
$ts = time();
// 时间戳转日期
echo date('Y-m-d H:i:s', $ts); // 按服务器时区
echo gmdate('Y-m-d H:i:s', $ts); // UTC
// 日期转时间戳
$ts2 = strtotime('2024-04-06 10:30:00'); // 按服务器时区
MySQL
-- 获取当前时间戳
SELECT UNIX_TIMESTAMP();
-- 时间戳转日期
SELECT FROM_UNIXTIME(1712374200);
SELECT FROM_UNIXTIME(1712374200, '%Y-%m-%d %H:%i:%s');
-- 日期转时间戳
SELECT UNIX_TIMESTAMP('2024-04-06 10:30:00');
时区:最容易出错的地方
时间戳本身是时区无关的,但把时间戳转成人类可读的日期时,必须指定时区。不指定,就用系统默认时区,这在服务器部署时是隐患:本地开发机可能是 CST(+8),服务器是 UTC(+0),同一个时间戳显示出来差 8 小时。
最佳实践:
- 存储和传输统一用 UTC 时间戳,不要存带时区的字符串
- 显示给用户时再转换到用户所在时区,转换逻辑放在前端或明确指定时区
- 代码里永远不要依赖系统默认时区,显式传入时区参数
# 错误示范:依赖系统时区
datetime.fromtimestamp(ts) # 行为取决于部署环境
# 正确做法:显式指定时区
datetime.fromtimestamp(ts, tz=timezone.utc)
2038年问题
Unix 时间戳最初用 32 位有符号整数存储,最大值是 2147483647,对应的时间是 2038年1月19日 03:14:07 UTC。超过这个时刻,32 位整数会溢出,变成负数,时间会回到 1901 年。
这就是 Y2K38 问题(2038年问题),类似于当年的千年虫。
好消息是:大多数现代系统已经迁移到 64 位整数存储时间戳,64 位时间戳能表示到约 2920 亿年后,完全不用担心。需要关注的是老旧系统、嵌入式设备,以及部分数据库的 TIMESTAMP 类型(MySQL 的 TIMESTAMP 就有 2038 上限,DATETIME 没有)。
在线工具
需要快速把一个时间戳转成日期,或者把某个日期转成时间戳,可以使用 toolshu.com 的时间戳在线转换工具。支持秒级和毫秒级,支持全球时区切换,无需安装任何软件。



加载中...