datetime相减直接得timespan最安全;须统一kind为utc避免时区偏差;days仅取日字段值,totaldays才表示总天数;高精度耗时测量应优先用stopwatch。

DateTime 相减直接得 TimeSpan,这是最安全、最省事的方式;绕开它去手动算年月日或用 Ticks 差值再构造 TimeSpan,反而容易出错。
用 DateTime 直接相减是最推荐的做法
别被“Subtract 方法”“Ticks 差”这些说法带偏——end - start 这个表达式本身就被 .NET 设计为返回 TimeSpan,语义清晰,底层已处理好所有精度和溢出逻辑。
- 写
endTime - startTime和写endTime.Subtract(startTime)效果完全一样,但前者更直观、更少打字 - 避免用
new TimeSpan(endTime.Ticks - startTime.Ticks):虽然能跑通,但若Ticks差为负,会得到负的TimeSpan,而Days、Hours等字段行为可能反直觉(比如-36 小时的Days是-1,Hours是12) - 如果只是测执行耗时,优先用
Stopwatch,它不依赖系统时钟,精度更高、无时区干扰
TimeSpan.Days 和 TimeSpan.TotalDays 完全不是一回事
这是新手掉坑最多的地方。你以为 Days 就是“总共多少天”?不是。
-
TimeSpan.Days只取TimeSpan内部“日”字段的整数值,范围固定在 -24 到 +24,超过 24 天就会“截断”——比如 35.8 天的TimeSpan,.Days是35;但 100.2 天的TimeSpan,.Days居然是4(因为 100 % 24 = 4) -
TimeSpan.TotalDays才是你想要的总天数,返回double,含小数部分;要取整数天数,用(int)Math.Floor(ts.TotalDays)或Math.Truncate(ts.TotalDays),而不是ts.Days - 同理:
.Hours≠TotalHours,.Minutes≠TotalMinutes——所有带Total前缀的属性才表示“换算成该单位的总值”
必须统一 DateTime.Kind,否则差值可能偏差数小时
一个 DateTime 值是否带时区信息,由它的 Kind 属性决定:Unspecified、Local 或 Utc。混着减,结果不可靠。
- 例如:
DateTime.Now(Local)减DateTime.UtcNow(Utc),在东八区会固定多出 8 小时,且夏令时切换时还会变 - 正确做法:全部转成
Utc再算,用dt.ToUniversalTime();注意Unspecified值调用ToUniversalTime()会被默认按本地时区解释,有风险;更稳妥的是从源头确保数据存的是Utc - 数据库读出来的可空
DateTime?,先判空再转换,否则null.Value或null.ToUniversalTime()都会抛NullReferenceException
别在业务逻辑里用 DateTime.Now 算跨度
除非你明确需要“用户本地时间视角下的差值”,否则一律用 DateTime.UtcNow。真实场景中,跨服务器、跨地域、跨客户端的时间比较,只有 Utc 是唯一可靠锚点。
-
DateTime.Now在不同机器上可能因系统时区/夏令时设置不同而指向不同绝对时刻,导致差值漂移 - API 返回的时间字段,如果没注明时区,大概率是
Unspecified,不能直接拿来减;应约定服务端始终返回 ISO 8601 UTC 格式(如"2026-04-12T16:09:00Z"),客户端用DateTime.Parse(..., null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)解析 - 日志、审计、定时任务等对时间一致性要求高的地方,
UtcNow是铁律
真正麻烦的从来不是“怎么算”,而是“用什么时间算”和“算出来怎么解释”。Kind 不一致、Days 和 TotalDays 混用、忽略可空性——这三处出错频率远高于语法本身。










