只要把两个问题定下来,转换本身很简单
Unix 时间戳 1700000000 转换结果是:
2023-11-14T22:13:20Z
2023 年 11 月 14 日星期二 22:13:20 UTC
在 JavaScript 里,一行就够:
new Date(1700000000 * 1000).toISOString();
// "2023-11-14T22:13:20.000Z"
代码是最容易的部分。真正该在相信结果之前回答的,是这两个问题:
- 输入的单位是秒、毫秒、微秒,还是纳秒?
- 结果该按 UTC 显示,还是按某个具体的本地时区?
单位搞错,一个 2023 年的事件就落到了 1970 年。时区搞错,瞬间倒是没错,但用户看到的小时数、甚至那个日历日期,都不是他预期的。
第一步:把单位坐实
对于接近今天的时间戳,数位数可以做第一道判断。但这是经验规则,不是数据约定。
| 示例 | 大概率的单位 | 常见来源 | 按秒收的 API 需要怎么处理 |
|---|---|---|---|
1700000000 |
秒 | Unix/POSIX API、PHP、Python | 原样使用 |
1700000000000 |
毫秒 | JavaScript、Java、业务数据库 | 除以 1,000 |
1700000000000000 |
微秒 | 数据仓库、事件流 | 除以 1,000,000 |
1700000000000000000 |
纳秒 | Go、链路追踪、遥测 | 除以 1,000,000,000 |
那个经典的单位 bug 很容易复现:
new Date(1700000000).toISOString();
// "1970-01-20T16:13:20.000Z" —— 秒被当成了毫秒
new Date(1700000000 * 1000).toISOString();
// "2023-11-14T22:13:20.000Z"
但别把数位数的规则固化成长期的业务逻辑。9 位的值可能是 2001 年之前的合法 Unix 时间戳,而到 2286 年 Unix 秒会变成 11 位。更要紧的是,0 或 1 这种小测试值,压根不告诉你它想表达什么单位。
在系统边界上,请用能自带约定的名字:createdAtSeconds、created_at_ms、eventTimeMicros。如果这个值来自 API、webhook、数据库列、JWT 或 CSV 导出,那它的文档说了算,优先级高于任何靠长度做的猜测。
本站的转换器能自动把毫秒和常规的 Unix 秒区分开。如果输入是微秒或纳秒,请先自行折算成秒或毫秒再粘贴进来。
第二步:把瞬间和它的显示分开
Unix 时间表示的是从 1970-01-01 00:00:00 UTC 这个 Unix 纪元起流逝的时间。在日常开发里,它标识的是一个瞬间。它内部并不包含纽约、东京或服务器本地时区这类信息。
The Open Group 的 POSIX 定义比“1970 年以来的秒数”这种简写要精确得多,尤其是在闰秒方面。但对普通应用的转换来说,好用的心智模型依然是:把数字解码成一个瞬间,再把这个瞬间渲染到选定的时区里。
同一个值在四个时区里的样子:
| 时区 | 1700000000 对应的本地日期时间 |
|---|---|
UTC |
2023-11-14 22:13:20 |
America/New_York |
2023-11-14 17:13:20(EST) |
Asia/Tokyo |
2023-11-15 07:13:20(JST) |
Australia/Sydney |
2023-11-15 09:13:20(AEDT) |
同一个事件,可纽约和东京的日历上写的日期都不一样。时间戳没有移动,变的只是钟面表示。
把下面这套当默认策略:
- 日志、API 负载、数据库比较和审计数据一律保持 UTC。
- 在展示层才按用户的 IANA 时区(比如
America/New_York)格式化。 - 当用户的本地时间语境重要时,把 IANA 时区另外存一份。
- 别把一个光秃秃的本地日期时间字符串,当成某个瞬间的唯一记录。
按任务挑对应的转换方式
| 任务 | 推荐做法 |
|---|---|
| 查看单个时间戳 | 用 Epoch 转日期工具 |
| JavaScript 秒 | new Date(seconds * 1000) |
| JavaScript 毫秒 | new Date(milliseconds) |
| Python 的 UTC datetime | datetime.fromtimestamp(seconds, tz=timezone.utc) |
| Linux 输出 UTC | date -u -d @seconds |
| macOS 输出 UTC | date -u -r seconds |
| PostgreSQL | to_timestamp(seconds) |
| MySQL | 先确认会话时区,再用 FROM_UNIXTIME(seconds) |
| SQLite | datetime(seconds, 'unixepoch') |
| BigQuery | TIMESTAMP_SECONDS、TIMESTAMP_MILLIS 或 TIMESTAMP_MICROS |
| Excel 秒 | =A1/86400 + DATE(1970,1,1) |
| Google 表格秒 | =EPOCHTODATE(A1, 1) |
觉得某次转换不对时,先把 UTC 的值算出来。只要那个瞬间是对的,本地格式化就是另一个小得多的独立问题了。
JavaScript:记住 Date 用的是毫秒
JavaScript 的 Date 时间戳值是自 Unix 纪元起的毫秒数。Unix 秒要乘以 1,000:
const createdAtSeconds = 1700000000;
const createdAt = new Date(createdAtSeconds * 1000);
createdAt.toISOString();
// "2023-11-14T22:13:20.000Z"
13 位的毫秒值本来就是 Date 期待的单位:
const createdAtMs = 1700000000000;
new Date(createdAtMs).toISOString();
// "2023-11-14T22:13:20.000Z"
面向用户的文案,请显式设置 timeZone。否则结果取决于执行这段代码的浏览器、服务器、容器或测试运行器。
const formatter = new Intl.DateTimeFormat("zh-CN", {
timeZone: "America/New_York",
dateStyle: "medium",
timeStyle: "short",
});
formatter.format(new Date(1700000000 * 1000));
// "2023年11月14日 17:13"
如果你的应用知道单位,就把它做成参数,别靠猜:
function unixToDate(value, unit = "seconds") {
if (!Number.isFinite(value)) {
throw new TypeError("timestamp must be a finite number");
}
if (unit === "seconds") return new Date(value * 1000);
if (unit === "milliseconds") return new Date(value);
if (unit === "microseconds") return new Date(Math.trunc(value / 1000));
throw new RangeError(`Unsupported unit: ${unit}`);
}
unixToDate(1700000000, "seconds").toISOString();
// "2023-11-14T22:13:20.000Z"
表单和 JSON 里的值要多留个心眼。JavaScript 会接受一些技术上合法、但多半不是你本意的输入:
new Date(null).toISOString();
// "1970-01-01T00:00:00.000Z"
new Date(undefined).toString();
// "Invalid Date"
另外,纳秒级的纪元值超出了 JavaScript Number 的精确整数范围。在你有意识地降低精度之前,请把它们保留为 BigInt 或字符串。
Python:构造一个带时区的 UTC datetime
构造 datetime 的时候就把时区传进去:
from datetime import datetime, timezone
created_at = datetime.fromtimestamp(1700000000, tz=timezone.utc)
created_at.isoformat()
# '2023-11-14T22:13:20+00:00'
不传 tz 的话,fromtimestamp() 会用机器的本地时区,并返回一个 naive datetime:
datetime.fromtimestamp(1700000000)
# 结果取决于机器的本地时区
调用按秒收的 API 之前,先把亚秒单位折算好:
datetime.fromtimestamp(1700000000000 / 1_000, tz=timezone.utc)
datetime.fromtimestamp(1700000000000000 / 1_000_000, tz=timezone.utc)
Python 3.11 及以后还提供了 datetime.UTC:
from datetime import UTC, datetime
datetime.fromtimestamp(1700000000, tz=UTC)
新代码请避开 datetime.utcfromtimestamp()。它返回 naive 对象,从 Python 3.12 起已被弃用;文档给出的替代写法是 datetime.fromtimestamp(timestamp, UTC)。
对于远超正常工作范围的日期,fromtimestamp() 可能抛出 OverflowError 或 OSError,具体看平台。单位错误经常就是以范围错误的形式冒出来的——这也正是“先验证单位、再怪日期库”的好理由。
PHP:有意识地在 UTC 和具名时区之间做选择
gmdate() 按 UTC 格式化 Unix 秒:
echo gmdate('c', 1700000000);
// 2023-11-14T22:13:20+00:00
相比之下,date() 用的是 PHP 配置的默认时区。应用代码若需要具名时区,DateTimeImmutable 能让转换保持显式:
$instant = new DateTimeImmutable('@1700000000');
echo $instant
->setTimezone(new DateTimeZone('Asia/Tokyo'))
->format(DateTimeInterface::ATOM);
// 2023-11-15T07:13:20+09:00
这些 API 收的都是秒。毫秒输入请先除以 1,000。
Java:用 Instant 表示时间轴上的那个点
java.time 把单位直接写进了方法名里:
Instant instant = Instant.ofEpochSecond(1700000000L);
instant.toString();
// 2023-11-14T22:13:20Z
毫秒:
Instant instant = Instant.ofEpochMilli(1700000000000L);
只有在需要本地日历字段时,才附加时区:
ZonedDateTime tokyo = Instant
.ofEpochSecond(1700000000L)
.atZone(ZoneId.of("Asia/Tokyo"));
老的 java.util.Date(long) 收的是毫秒,所以直接把 Unix 秒传进去,就会得到那个眼熟的 1970 错误。
Oracle Java Instant · Java 时间戳代码片段
C# 和 .NET:让 DateTimeOffset 承载这个瞬间
用名字里写明输入单位的那个方法:
var createdAt = DateTimeOffset.FromUnixTimeSeconds(1700000000);
createdAt.ToString("O");
// 2023-11-14T22:13:20.0000000+00:00
毫秒:
var createdAt = DateTimeOffset.FromUnixTimeMilliseconds(1700000000000);
展示时再做时区转换:
var zone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var local = TimeZoneInfo.ConvertTime(createdAt, zone);
时区标识的支持情况取决于操作系统和 .NET 的部署方式。别想当然地以为某个 IANA 或 Windows ID 在哪儿都能用,请在应用实际运行的平台上测一遍。
微软 DateTimeOffset.FromUnixTimeSeconds · C# 时间戳代码片段
Go:按精度选对应的辅助函数
Go 最早的构造函数收的是秒加纳秒偏移:
t := time.Unix(1700000000, 0).UTC()
fmt.Println(t.Format(time.RFC3339))
// 2023-11-14T22:13:20Z
现在的 Go 也为常见的亚秒纪元值提供了辅助函数:
time.UnixMilli(1700000000000).UTC()
time.UnixMicro(1700000000000000).UTC()
对于完整的纳秒纪元计数,这种写法能保住原本的单位含义:
time.Unix(0, 1700000000000000000).UTC()
要产出钟面输出时,加载一个 IANA location:
loc, err := time.LoadLocation("Europe/Berlin")
if err != nil {
log.Fatal(err)
}
fmt.Println(time.Unix(1700000000, 0).In(loc).Format(time.RFC3339))
用 Linux 和 macOS 的 date 命令转换 Unix 时间戳
别扭的地方不在 Unix 时间本身,而在于 date 这个命令在各处并不是同一个东西。
| 环境 | 常见实现 | 需要记住的点 |
|---|---|---|
| Ubuntu、Debian、Fedora、RHEL | GNU coreutils 的 date |
支持 -d、@SECONDS、%N、--rfc-3339、--iso-8601 |
| macOS 和 FreeBSD | BSD 的 date |
纪元转日期要用 -r SECONDS |
| Alpine 和精简 CI 镜像 | 往往是 BusyBox 的 date |
选项少得多,请在具体镜像上实测 |
| 装了 GNU coreutils 的 macOS | GNU 的 gdate |
要用 Linux 兼容命令就调 gdate |
在 GNU/Linux 上,纪元秒前面要加 @。如果结果需要在笔记本、服务器、容器和 CI 之间保持一致,就加上 -u:
date -u -d @1700000000
date -u -d @1700000000 +'%Y-%m-%dT%H:%M:%SZ'
# 2023-11-14T22:13:20Z
date -u -d @1700000000 --rfc-3339=seconds
# 2023-11-14 22:13:20+00:00
只有当你确实想要机器本地时区时,才省掉 -u。想在不改动主机时钟的前提下渲染某个具名时区,就为这条命令单独设 TZ:
TZ=America/New_York date -d @1700000000 +'%Y-%m-%d %H:%M:%S %Z'
# 2023-11-14 17:13:20 EST
TZ=Asia/Tokyo date -d @1700000000 +'%Y-%m-%d %H:%M:%S %Z'
# 2023-11-15 07:13:20 JST
这个 TZ= 前缀改变的只是显示出来的钟面值,它并不改变时间戳所代表的那个瞬间。
在 shell 里转换纪元毫秒
大多数命令行时间戳工具读的是秒。把一个 13 位的毫秒值直接丢给 date -d @...,它会按秒解释,于是给你一个遥远未来的日期。如果整秒精度就够用,先除以 1,000:
ms=1700000000000
seconds=$((ms / 1000))
date -u -d "@$seconds" +'%Y-%m-%dT%H:%M:%SZ'
# 2023-11-14T22:13:20Z
如果小数毫秒也要保留,就把值拆成秒和最后三位:
ms=1700000000123
seconds=${ms%???}
millis=${ms#$seconds}
date -u -d "@$seconds.$millis" +'%Y-%m-%dT%H:%M:%S.%3NZ'
# 2023-11-14T22:13:20.123Z
变量名里也带上单位,比如 created_at_s、created_at_ms、client_sent_at_ms。这比在脚本深处埋一段聪明的位数猜测,能挡掉多得多的错误。
在 macOS 和 FreeBSD 上使用 BSD 的 date
BSD 的 date 用 -r SECONDS,而不是 GNU 的 -d @SECONDS:
date -u -r 1700000000 +'%Y-%m-%dT%H:%M:%SZ'
# 2023-11-14T22:13:20Z
TZ=America/New_York date -r 1700000000 +'%Y-%m-%d %H:%M:%S %Z'
# 2023-11-14 17:13:20 EST
同一个选项在两边可能是完全不同的意思:GNU 的 date -r FILE 显示文件的修改时间,而 BSD 的 date -r SECONDS 转换纪元秒。如果 macOS 上的脚本需要 Linux 语法,就装上 GNU coreutils 并显式调用 gdate:
gdate -u -d @1700000000 +'%Y-%m-%dT%H:%M:%SZ'
调用 date 之前先校验输入
对于脚本参数,请直接拒掉意料之外的输入,而不是默默猜单位:
ts=${1:-}
case "$ts" in
''|*[!0-9]*)
printf 'usage: %s UNIX_SECONDS\n' "$0" >&2
exit 2
;;
esac
date -u -d "@$ts" +'%Y-%m-%dT%H:%M:%SZ'
这个例子是有意只接受正整数秒的。如果负时间戳或毫秒也算合法,那就把单位做成一个独立参数,别让脚本自己去推断。
date 命令常见故障排查
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
date: illegal option -- d |
用的是 macOS/BSD 的 date |
改用 date -u -r SECONDS,或安装 GNU 的 gdate |
| 输出在 1970 年附近 | 秒被别处按毫秒的 API 处理了 | 到边界上去查单位 |
| 输出在遥远的未来 | 毫秒被当成秒传给了 date |
除以 1,000,或保留小数部分 |
date +%s%3N 结尾出现 3N |
BSD 的 date 不支持 GNU 的 %N |
改用 Python、Node.js、Perl 或 gdate |
| Ubuntu 上好好的,Alpine 上就挂 | BusyBox 的 date 选项更少 |
在具体镜像上实测,或装 coreutils |
| 笔记本和服务器显示的小时不一样 | 用了本地时区 | 加 -u,或显式写 TZ=区域/城市 |
如果脚本得同时兼容 GNU 和 BSD 的 date,加一小段平台分支很实用:
if date --version >/dev/null 2>&1; then
date -u -d @1700000000 +'%Y-%m-%dT%H:%M:%SZ'
else
date -u -r 1700000000 +'%Y-%m-%dT%H:%M:%SZ'
fi
在可复现构建里解析 SOURCE_DATE_EPOCH
很多构建工具把 SOURCE_DATE_EPOCH 识别为纪元秒,好让生成的文件拿到稳定的时间戳:
export SOURCE_DATE_EPOCH=1700000000
date -u -d "@$SOURCE_DATE_EPOCH" +'%Y-%m-%dT%H:%M:%SZ'
# 2023-11-14T22:13:20Z
除非使用它的工具明确写了别的单位,否则这个值就保持秒。
GNU Coreutils date 手册 · Linux date(1) 手册 · FreeBSD date(1) 手册
SQL:转换和渲染用的可能是不同的时区
数据库函数写起来很简洁,但它们显示出来的结果可能取决于会话时区。
| 数据库 | Unix 秒转换 | 需要注意的行为 |
|---|---|---|
| PostgreSQL | to_timestamp(1700000000) |
返回 timestamp with time zone,显示跟随会话时区 |
| MySQL | FROM_UNIXTIME(1700000000) |
按当前会话时区渲染 |
| SQLite | datetime(1700000000, 'unixepoch') |
除非加 localtime,否则返回 UTC |
| BigQuery | TIMESTAMP_SECONDS(1700000000) |
用单位明确的构造函数 |
PostgreSQL:
SELECT to_timestamp(1700000000) AT TIME ZONE 'UTC';
-- 2023-11-14 22:13:20
MySQL:
SET time_zone = '+00:00';
SELECT FROM_UNIXTIME(1700000000);
-- 2023-11-14 22:13:20
SQLite:
SELECT datetime(1700000000, 'unixepoch');
-- 2023-11-14 22:13:20
BigQuery 把精度写得很明白:
SELECT TIMESTAMP_SECONDS(1700000000);
SELECT TIMESTAMP_MILLIS(1700000000000);
SELECT TIMESTAMP_MICROS(1700000000000000);
做数据迁移时,不妨在数据审计完成之前,把原始整数和解析后的时间戳都留着。万一有记录是按错误单位导进来的,你还能顺着这条线索找回去。
Excel:先把秒换算成天,再设置单元格格式
Excel 里日期是以“天”为单位的序列号存储的。把 Unix 秒除以一天的秒数,再加上纪元日期:
=A1/86400 + DATE(1970,1,1)
其他单位:
=A1/86400000 + DATE(1970,1,1) // 毫秒
=A1/86400000000 + DATE(1970,1,1) // 微秒
如果单元格显示成小数,套一个日期时间数字格式就行——公式本身可能已经是对的。
电子表格带来两个实际风险:它不给序列值附加时区,而且 CSV 导入时可能把大整数显示成科学计数法。如果某列时间戳对精度敏感,就按文本导入,并且在列名里写清楚它是 UTC 还是本地时间。
Google 表格:用 EPOCHTODATE 并写明单位
Google 表格通过一个单位参数支持秒、毫秒和微秒:
=EPOCHTODATE(A1, 1) // 秒
=EPOCHTODATE(A1, 2) // 毫秒
=EPOCHTODATE(A1, 3) // 微秒
结果是 UTC,不是表格的本地时区。另外这个函数不接受负时间戳。
对于正的 Unix 秒,按天换算的公式也是一种选择:
=A1/86400 + DATE(1970,1,1)
在多人共享的表格里,created_at_seconds_utc、created_at_ms_utc 这样的表头,能让下一个人不必去逆向推导单位和时区。
按症状排查错误日期
| 症状 | 可能原因 | 先试这个 |
|---|---|---|
| 落在 1970 年 1 月 | 秒被传给了按毫秒收的 API | 乘 1,000 |
| 落在几千年后的未来 | 毫秒或微秒被当成秒 | 除 1,000 或 1,000,000 |
| 笔记本和服务器结果不一致 | 各自用了默认时区 | 显式指定 UTC 或某个 IANA 时区 |
| Python 的 datetime 没有偏移 | 创建的是 naive datetime | 传 tz=timezone.utc |
| MySQL 输出整体差了几小时 | 会话时区不同 | 设置或转换会话时区 |
| Google 表格拒绝这个值 | 负时间戳或单位不对 | 检查该函数支持的范围和单位参数 |
| CSV 里的值掉了几位 | 电子表格的数值强制转换 | 按文本导入,或用整数安全的工具 |
| 1970 年之前的日期失败 | 目标平台范围受限 | 就在那个系统上实测负时间戳 |
绝大多数时间戳转换故障,最后都能归到单位、时区或范围这三样上。在动手替换本来能跑的日期代码之前,先把这三个查一遍。
在测试里留一小组已知值
只测一个顺利路径的值是不够的。下面这些用例能把最常见的假设都翻出来:
| 时间戳 | 期望的 UTC 值 | 它在测什么 |
|---|---|---|
0 |
1970-01-01T00:00:00Z |
纪元边界 |
-1 |
1969-12-31T23:59:59Z |
是否支持负时间戳 |
1000000000 |
2001-09-09T01:46:40Z |
较早的 10 位秒值 |
1700000000 |
2023-11-14T22:13:20Z |
普通秒值 |
2147483647 |
2038-01-19T03:14:07Z |
32 位有符号边界 |
1700000000000 |
2023-11-14T22:13:20Z |
毫秒输入 |
1700000000000000 |
2023-11-14T22:13:20Z |
微秒输入 |
如果有用户界面,至少再加一条时区断言:
1700000000 在 America/New_York = 2023-11-14 17:13:20 EST
1700000000 在 Asia/Tokyo = 2023-11-15 07:13:20 JST
这条测试能抓住那种“瞬间算对了、但展示用错了时区”的转换。
值得带进生产的那条准则
把 Unix 时间转成可读日期,其实不是一个操作,而是一条短流水线:
原始整数 → 已知单位 → UTC 瞬间 → 带时区的显示
这几个阶段不该混在一起。在边界上把单位定名,中间一路保持 UTC 瞬间,只有在渲染给人看的时候才挑时区。这份约定一旦立住,各语言的具体写法就只是例行公事,而不是什么玄学。
官方参考资料
- The Open Group:Seconds Since the Epoch
- MDN
Date构造函数 - Python
datetime.fromtimestamp - PHP
gmdate - Oracle Java
Instant - 微软
DateTimeOffset.FromUnixTimeSeconds - Go 的
time包 - PostgreSQL 日期时间函数
- MySQL 日期时间函数
- SQLite 日期时间函数
- BigQuery 时间戳函数
- Google 表格
EPOCHTODATE - 微软 Excel
DATE函数 - GNU Coreutils
date手册 - Linux
date(1)手册 - FreeBSD
date(1)手册
相关文章
Frequent questions:
- Q: 怎么把 Unix 时间转成日期?
- A: 先认单位。1700000000 按 Unix 秒算,1700000000000 按毫秒算,两者都表示 2023-11-14T22:13:20Z。先把值按 UTC 解码,再把这个瞬间格式化到你需要的时区。
- Q: 为什么我的 Unix 时间戳转出来是 1970 年?
- A: 通常是单位对不上。JavaScript 的 Date 收的是毫秒,所以 new Date(1700000000) 会把它读成纪元之后 17 亿毫秒。Unix 秒请写成 new Date(1700000000 * 1000)。
- Q: JavaScript 里怎么把 Unix 时间转成日期?
- A: 秒用 new Date(seconds * 1000).toISOString();毫秒直接传给 new Date(milliseconds)。要输出本地格式,用 Intl.DateTimeFormat 并显式指定 timeZone。
- Q: Python 里怎么把 Unix 时间转成日期?
- A: 用 datetime.fromtimestamp(seconds, tz=timezone.utc)。tz 这个参数很关键——不传它,返回的是机器本地时区的 datetime。新代码请避开 utcfromtimestamp(),它返回 naive datetime 且已被弃用。
- Q: SQL 里怎么把 Unix 时间转成日期?
- A: 用你数据库的原生函数:PostgreSQL 的 to_timestamp(seconds)、MySQL 的 FROM_UNIXTIME(seconds)、SQLite 的 datetime(seconds, 'unixepoch'),或者 BigQuery 的 TIMESTAMP_SECONDS / TIMESTAMP_MILLIS / TIMESTAMP_MICROS。比较渲染出来的字符串之前,先确认数据库会话时区。
- Q: 13 位的时间戳怎么转成日期?
- A: 当下的 13 位纪元值通常是毫秒。JavaScript 的 Date、Java 的 Instant.ofEpochMilli 和 .NET 的 FromUnixTimeMilliseconds 都直接收毫秒。而 Python、PHP、PostgreSQL、MySQL 以及很多 shell 工具通常要秒,所以得先除以 1000。
- Q: 16 位的时间戳怎么转成日期?
- A: 当下的 16 位纪元值通常是微秒。给按秒收的 API 就除以 1,000,000,或者直接用原生的微秒函数,比如 BigQuery 的 TIMESTAMP_MICROS 或 Go 的 time.UnixMicro。
- Q: Unix 时间该显示成 UTC 还是本地时间?
- A: 日志、接口、数据库导出、审计记录和跨系统排查,一律用 UTC。仪表盘、截止时间、收据和日历类界面,用用户的 IANA 时区。原始的那个瞬间始终是唯一事实来源。
- Q: Unix 时间可以是负数吗?
- A: 可以。负的 Unix 秒表示 1970-01-01 UTC 之前的瞬间。很多语言都支持,但电子表格、数据库和操作系统调用的范围可能窄得多,所以 1970 年以前的数据请在真实的目标系统上实测。
- Q: Unix 时间和纪元时间是一回事吗?
- A: 在大多数开发语境里,Unix 时间、Unix 时间戳、纪元时间和 POSIX 时间都指从 1970-01-01 00:00:00 UTC 起算的计数。但单位没有保证,所以务必确认这个系统存的是秒、毫秒、微秒还是纳秒。
- Q: 为什么 macOS 上 date -d 会报错?
- A: macOS 自带的是 BSD date,不是 GNU date。转换纪元秒请用 date -u -r 1700000000,或者装上 GNU coreutils 后运行 gdate -u -d @1700000000。
- Q: 为什么 macOS 上 date +%s%3N 会打印出 3N?
- A: %N 这个纳秒格式是 GNU date 的扩展。BSD date 可能原样打印 3N,所以要拿到可移植的当前纪元毫秒,请用 Python、Node.js、Perl 或 GNU 的 gdate。