alltools.one
Development
2025-07-05
7 min
alltools.one Team
UnixTimestampEpochTime ZoneDate

Unix 时间戳详解:转换与常见陷阱

Unix 时间戳是编程中最简单但最容易被误解的概念之一。它是自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的秒数 — 这一时刻被称为 Unix 纪元。尽管概念简单,时间戳却是与时区、精度和溢出相关的 bug 来源。

什么是 Unix 纪元?

Unix 纪元 — 1970 年 1 月 1 日 00:00:00 UTC — 被选为 Unix 时间的起点。每个时间戳都是相对于这一时刻的测量值:

时间戳日期和时间(UTC)
01970 年 1 月 1 日 00:00:00
864001970 年 1 月 2 日 00:00:00
10000000002001 年 9 月 9 日 01:46:40
17000000002023 年 11 月 14 日 22:13:20
20000000002033 年 5 月 18 日 03:33:20

负时间戳表示纪元之前的日期。例如,-86400 是 1969 年 12 月 31 日。

使用我们的时间戳转换器即时转换时间戳。

秒与毫秒

这是最常见的混淆来源。不同系统使用不同的精度:

系统精度示例
Unix/POSIX1700000000
JavaScript毫秒1700000000000
Java (System.currentTimeMillis)毫秒1700000000000
Python (time.time)秒(浮点数)1700000000.123
PostgreSQL (extract epoch)秒(浮点数)1700000000.123456

经验法则:如果数字有 13 位,则是毫秒。如果有 10 位,则是秒。

// JavaScript 返回毫秒
const nowMs = Date.now();          // 1700000000000
const nowSec = Math.floor(nowMs / 1000);  // 1700000000

时区处理

Unix 时间戳始终是 UTC。它们不包含时区信息。这实际上是一个特性 — 它提供了一个通用参考点。

当将时间戳转换为人类可读的日期时,混淆就出现了:

const ts = 1700000000;
const date = new Date(ts * 1000);

date.toUTCString();      // "Tue, 14 Nov 2023 22:13:20 GMT"
date.toLocaleString();    // 取决于用户的本地时区
date.toISOString();       // "2023-11-14T22:13:20.000Z"

最佳实践:以 UTC 存储和传输时间戳。仅在显示层、尽可能靠近用户的位置转换为本地时间。

2038 年问题

传统 Unix 系统将时间戳存储为 32 位有符号整数。最大值为 2,147,483,647,对应 2038 年 1 月 19 日 03:14:07 UTC

在此时刻之后,32 位时间戳溢出为负值,回绕到 1901 年 12 月 13 日。这类似于千年虫问题。

当前状态

  • 大多数现代系统使用 64 位时间戳(可使用到 2920 亿年后)
  • Linux 内核自 5.6 版本(2020 年)起已完成 64 位时间戳清理
  • 嵌入式系统和遗留数据库仍有风险
  • 如果你正在构建处理 2038 年之后日期的软件,请验证你的时间戳存储

不同语言中的转换

JavaScript

// 当前时间戳(秒)
const now = Math.floor(Date.now() / 1000);

// 时间戳转 Date
const date = new Date(1700000000 * 1000);

// Date 转时间戳
const ts = Math.floor(new Date('2023-11-14').getTime() / 1000);

Python

import time, datetime

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

# 时间戳转 datetime
dt = datetime.datetime.fromtimestamp(1700000000, tz=datetime.timezone.utc)

# datetime 转时间戳
ts = int(dt.timestamp())

SQL (PostgreSQL)

-- 当前时间戳
SELECT EXTRACT(EPOCH FROM NOW());

-- 时间戳转日期
SELECT TO_TIMESTAMP(1700000000);

-- 日期转时间戳
SELECT EXTRACT(EPOCH FROM '2023-11-14'::timestamp);

常见陷阱

1. 混淆秒和毫秒

如果日期显示为 1970 年 1 月,你可能在期望毫秒的地方传入了秒(或反之)。始终检查 API 期望的精度。

2. 日期字符串中忽略时区

解析 "2023-11-14" 时不指定时区会在本地时区创建日期,这因服务器位置而异。始终包含时区:"2023-11-14T00:00:00Z"

3. 浮点精度

将时间戳存储为浮点数时,可能会丢失毫秒以上的精度。对于微秒或纳秒精度,请使用适当乘数的整数。

4. 闰秒

Unix 时间戳不考虑闰秒。Unix 日始终恰好是 86,400 秒,即使实际的 UTC 日偶尔有 86,401 秒。对于大多数应用来说,这无关紧要。对于科学或卫星应用,请使用 TAI(国际原子时)代替。

ISO 8601:人类可读的替代方案

虽然时间戳非常适合计算,但 ISO 8601 是人类可读日期表示的标准:

2023-11-14T22:13:20Z          # UTC
2023-11-14T17:13:20-05:00     # 东部时间
2023-11-14                     # 仅日期

大多数 API 应接受和返回 ISO 8601 字符串。在内部使用时间戳进行计算和存储。

常见问题

为什么 Unix 时间从 1970 年 1 月 1 日开始?

这个日期是在 1970 年代初贝尔实验室开发 Unix 时任意选择的。需要一个足够近的日期,以避免在遥远的过去浪费位。由于 32 位整数可以在每个方向存储约 68 年,从 1970 年开始覆盖了 1901 年到 2038 年的日期。

我应该在数据库中将日期存储为时间戳还是格式化字符串?

将日期存储为时间戳(整数或原生日期时间类型)以便高效排序、比较和运算。格式化字符串更难以正确查询和排序。大多数数据库具有处理此问题的原生日期时间类型。将字符串格式化保留给显示和 API 响应。

相关资源

Published on 2025-07-05
Unix Timestamps Explained: Conversion and Common Pitfalls | alltools.one