应优先使用 DateTime.UtcNow + 显式转换,避免 DateTime.Now 的时区陷阱;格式化统一用内建格式字符串(如 "yyyy-MM-dd HH:mm:ss" 或 "o"),禁用硬拼字符串;高频场景改用 DateOnly 或整数日期码提升性能。

用 DateTime.Now 拿当前时间,但要注意时区陷阱
直接写 DateTime.Now 就能拿到本地时区的时间,但它依赖系统设置,不是 UTC。如果你的程序要部署到服务器(比如 Linux 上跑 .NET 6+),而服务器时区是 UTC,DateTime.Now 会按服务器本地时区返回——可能和你本地开发环境不一致,导致日志时间错乱或定时逻辑偏差。
- 生产环境统一用
DateTime.UtcNow+ 显式转换,避免隐式时区依赖 -
DateTime.Now返回的是DateTimeKind.Local类型,序列化(如 JSON)时可能被错误解释为 UTC - 如果真要本地时间,确认服务器时区已正确配置,别靠代码“猜”
格式化用 ToString(),别硬拼字符串
写 dt.Year + "-" + dt.Month + "..." 看似可控,实则埋雷:月份/日期没补零、文化区域(culture)影响分隔符、无法处理 AM/PM。正确做法是用内建格式字符串,它自动适配当前线程 culture,也支持自定义。
- 常用安全格式:
dt.ToString("yyyy-MM-dd HH:mm:ss")(24 小时制,零填充) - 要兼容中文系统显示“年月日”,用
dt.ToString("yyyy年MM月dd日 HH:mm:ss"),引号包裹文字部分 - 避免用
"MM/dd/yyyy"这类易混淆格式,尤其在国际化场景下 - 如果必须固定 culture(比如 API 输出),显式传参:
dt.ToString("o")(ISO 8601 标准格式)
ToString("o") 和 ToString("s") 的区别很关键
这两个都是标准格式,但行为完全不同:"o" 输出带毫秒和时区偏移的完整 ISO 字符串(如 2024-05-20T14:23:15.123+08:00),"s" 只输出可排序的局部时间(2024-05-20T14:23:15),不含时区信息——它本质是 DateTimeKind.Unspecified 的表现,反序列化时容易误判为本地时间。
- 日志、API 响应、跨系统传输,优先用
"o" -
"s"仅适合内部排序或临时字符串比较,别用于时间交换 - 注意:
"o"对DateTimeKind.Unspecified值也会强行加本地偏移,所以源头最好明确 kind
性能敏感场景慎用字符串格式化
高频日志或循环中反复调用 ToString("yyyy-MM-dd") 会触发大量字符串分配,GC 压力明显。如果只是做日期归档(比如按天分目录),可以预计算 DateOnly(.NET 6+)或提取年月日整数字段。
- .NET 6+ 推荐:
DateOnly.FromDateTime(dt),再用ToString("yyyy-MM"),更轻量 - 老版本可用
dt.Year * 10000 + dt.Month * 100 + dt.Day得到整数日期码,比字符串快一个数量级 - 别为了“看着整洁”在热路径里拼接带前导零的字符串
时区、kind、格式字符串三者交织,最容易出问题的地方不在语法,而在“以为自己知道它怎么工作”。实际跑一次 DateTime.Now.Kind 和 DateTime.UtcNow.Kind,再打印它们的 "o" 格式,比读十遍文档管用。










