用 DateTimeOffset.FromUnixTimeSeconds 最稳:它安全、时区明确,默认按 UTC 解析;毫秒级需先 /1000 转秒级(用 long 防溢出),.NET 6+ 可用 FromUnixTimeMilliseconds 简化。

Unix 时间戳转 DateTime:用 DateTimeOffset.FromUnixTimeSeconds 最稳
Unix 时间戳(秒级)直接转 DateTime 不能硬除 1000 再加纪元——C# 里没内置“秒级转 DateTime”的静态方法,但 DateTimeOffset 提供了安全、时区明确的入口。它默认按 UTC 解析,后续再转本地或指定时区更可控。
常见错误是手写 new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp),看似可行,但容易忽略DateTimeKind 传播问题,尤其在序列化或跨时区传递时出错。
-
DateTimeOffset.FromUnixTimeSeconds(timestamp)返回带 UTC 偏移的值,可直接调.DateTime或.UtcDateTime - 若输入是毫秒级(如 JavaScript
Date.now()),先整除 1000:timestampMs / 1000,注意用long避免溢出 - 传入负数(1970 年前)也支持,但
DateTime最小值是 0001-01-01,超范围会抛ArgumentOutOfRangeException
毫秒级时间戳别漏掉/1000,但要注意整除截断
很多 API 返回的是毫秒级时间戳(13 位数字),比如 1717023600000。直接喂给FromUnixTimeSeconds 会当成“一千七百一十七万亿秒”,远超纪元后几百年,结果是公元 5 万年 + 的日期——程序不报错,但逻辑全歪。
正确做法是先转成 long 再整除 1000,不是强制转换或(int)ts/1000(32 位溢出风险高):
long tsMs = 1717023600000L; DateTime dt = DateTimeOffset.FromUnixTimeSeconds(tsMs / 1000).DateTime;
- 用
L后缀确保字面量是long,避免编译期隐式转int失败 - 不要用
Math.DivRem或Convert.ToInt64绕路,/1000 足够清晰 - 如果原始值来自 JSON 反序列化(如
long?),记得判空再除
转完要不要.ToLocalTime()?看场景
从 Unix 时间戳还原的时间本质是 UTC 时刻。是否调 .ToLocalTime() 取决于你后续怎么用它:
- 存数据库、发 HTTP API、做时间计算——保持 UTC(用
.UtcDateTime或.DateTime配合DateTimeKind.Utc) - 显示给用户看、写日志、生成报表——才需要转本地时区,此时用
.ToLocalTime() - 服务器部署在 Docker 或 Linux 容器里,
TimeZoneInfo.Local可能不是你预期的时区,建议显式指定:dt.ToLocalTime()只适合开发机调试
一个典型坑:把 UTC 时间直接当本地时间存进 SQL Server 的 datetime 字段,再查出来用 .ToLocalTime() 二次转换,结果多转了一次——时间就飘了 8 小时。
DateTimeOffset.FromUnixTimeMilliseconds是。NET 6+ 才有的甜点
.NET 6 起新增了DateTimeOffset.FromUnixTimeMilliseconds,省去手动除 1000 的步骤,代码更直白:
DateTime dt = DateTimeOffset.FromUnixTimeMilliseconds(1717023600000L).DateTime;
但它只在。NET 6 及以上可用,老项目(.NET Framework 4.8 / .NET Core 3.1)必须用FromUnixTimeSeconds + 手动换算。别在。csproj 里升级 TargetFramework 后忘了测兼容性——有些 NuGet 包在低版本运行时仍会走旧路径。
另外,这个方法不接受 null 或小于-62135596800000(对应 0001-01-01)的值,越界照样抛异常,不能假设它“更宽容”。
真正麻烦的从来不是转换函数本身,而是时间戳来源不清:前端传的?数据库存的?第三方 API 文档写的“timestamp”到底指秒还是毫秒?校验第一步永远该是打日志看原始值位数,而不是急着写转换逻辑。