土薯工具 Toolshu.com 登录 用户注册

Unix时间戳完全指南:原理、时区陷阱与各语言互转代码

原创 作者:bhnw 于 2026-04-06 22:49 发布 1次浏览 收藏 (0)

时间戳是什么

打开数据库,你可能会看到 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 小时。

最佳实践:

  1. 存储和传输统一用 UTC 时间戳,不要存带时区的字符串
  2. 显示给用户时再转换到用户所在时区,转换逻辑放在前端或明确指定时区
  3. 代码里永远不要依赖系统默认时区,显式传入时区参数
# 错误示范:依赖系统时区
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 的时间戳在线转换工具。支持秒级和毫秒级,支持全球时区切换,无需安装任何软件。

发现周边 发现周边
评论区

加载中...