应优先使用 DateTime.UtcNow 或 DateTimeOffset 处理时间,避免混用本地时间与 UTC 时间导致的时区错误;格式化需保留偏移信息,高频场景用 Stopwatch 替代 DateTime.Now。

直接用 DateTime.Now 就能拿到当前系统时间,但别急着赋值或显示——时区、精度、线程安全和格式化陷阱,一个没注意就出错。
为什么 DateTime.Now 和 DateTime.UtcNow 不能混用
很多逻辑 bug 来自把本地时间当 UTC 时间用,比如存数据库、跨服务传时间戳、做定时任务判断。Windows 系统默认返回的是本地时区(含夏令时偏移),而服务器常设为 UTC,DateTime.Now 在北京会多出 +8 小时,但这个 +8 不是显式记录在 DateTime.Kind 里的,容易误判。
-
DateTime.Now.Kind是Local,但不会自动帮你转成 UTC;手动调.ToUniversalTime()又可能因夏令时规则出错 - Web API 或微服务间传递时间,统一用
DateTime.UtcNow+ 显式注明“UTC”更稳妥 - 日志打点、缓存过期、定时器触发,优先选
DateTime.UtcNow,避免部署到不同时区机器后行为漂移
DateTimeOffset 才是带时区信息的靠谱选择
DateTime 本身不带时区上下文,Kind 属性只是个标记,不是真实偏移量。真正需要明确“几点几分在哪时区”,得用 DateTimeOffset。
-
DateTimeOffset.Now包含当前系统时区偏移(如 +08:00),序列化/反序列化时不会丢信息 - 数据库字段类型支持
datetimeoffset(SQL Server)或timestamptz(PostgreSQL),比存datetime更准确 - 和 JavaScript 的
Date().toISOString()互操作时,DateTimeOffset转字符串天然匹配 ISO 8601 带偏移格式
格式化输出时,ToString("yyyy-MM-dd HH:mm:ss") 会吃掉时区信息
用固定字符串格式输出 DateTime,本质是丢弃 Kind 或 Offset 的过程。一旦后续代码靠字符串再解析,就彻底失去原始时区线索。
- 显示给用户看:用
.ToString("g", culture)让系统按本地习惯格式化,而不是硬编码分隔符 - 写入日志或 API 响应:优先用
DateTimeOffset.ToString("o")(ISO 8601 round-trip 格式),它保留毫秒和偏移,比如"2024-05-22T14:30:45.123+08:00" - 别用
string.Format("{0:yyyy-MM-dd HH:mm:ss}", dt)处理跨时区数据——它不检查dt.Kind,本地时间被当成 UTC 输出也不报错
性能敏感场景下,DateTime.Now 比 Stopwatch 慢得多
高频获取当前时间(比如游戏帧循环、高频采样),DateTime.Now 是系统调用,有明显开销;而 Stopwatch 基于高精度计数器,快一个数量级。
- 只测间隔不用绝对时间?用
Stopwatch.GetTimestamp()+Stopwatch.Frequency算纳秒级差值 - 需要绝对时间戳又怕
DateTime.Now延迟波动?可启动时缓存一次DateTime.UtcNow,再用Stopwatch增量累加,定期校准 - 注意:
DateTime.UtcNow在 Windows 上底层调的是GetSystemTimeAsFileTime,虽比Now快,但仍不如Stopwatch
时区不是可选项,是必填项;格式化不是美化,是信息保全。哪怕只是写个本地小工具,也建议从第一行时间读取就决定好:我要的是“此刻”还是“那个时刻”,然后选对类型、留好偏移、避开字符串中间态。


















