最自然安全的方式是用 DateTime.Subtract() 或减号直接得 TimeSpan;注意 Kind 一致、结果可能为负需 Math.Abs();Days 与 TotalDays 不同,前者仅整数天后者含小数;格式化避免用 "d" 防溢出,跨日计算应先取 .Date。

用 DateTime.Subtract() 或减号得到 TimeSpan
两个 DateTime 相减,直接返回 TimeSpan 对象,这是最自然、最安全的方式。别用 DateTime.Compare() 或手动算 ticks 再除——既绕路又容易出错。
注意:结果可能是负的(比如 earlier.Subtract(later)),所以如果只关心间隔长度,得用 Math.Abs() 包一层。
-
var diff = later.Subtract(earlier);等价于later - earlier - 确保两个
DateTime的Kind一致(都是Utc或都是Local),否则跨时区比较会出偏差 - 如果其中一个来自数据库或 API,可能带有时区信息(如
DateTimeOffset),要先转成统一DateTime再减
TimeSpan.Days 和 TimeSpan.TotalDays 完全不是一回事
TimeSpan.Days 只返回“完整天数”部分,即去掉小时分钟秒后的整数天(范围是 -365 到 365)。而 TimeSpan.TotalDays 返回总天数(含小数),比如 2 天 12 小时 → Days == 2,TotalDays == 2.5。
- 要判断是否超过 3 天?用
ts.TotalDays > 3,别用ts.Days > 3 - 要显示“X 天 Y 小时”,才用
ts.Days搭配ts.Hours;但注意ts.Hours是剩余小时(0–23),不是总小时 -
ts.TotalSeconds是总秒数(含小数),ts.Seconds是“秒字段”(0–59)——同理,别混用
格式化输出时,ToString("d\.hh\:mm\:ss") 容易崩
自定义格式字符串里用 d 表示“天数”,但它默认只支持 0–365,超出就抛 FormatException。比如 400 天的 TimeSpan 用这个模板会直接失败。
- 安全做法是拼接:
$"{ts.Days}天{ts.Hours}小时{ts.Minutes}分{ts.Seconds}秒" - 如果必须用
ToString(),改用标准格式符"g"或"G"("G"带前导零和毫秒,"g"更简洁) -
ts.ToString("c")(规范格式)对大于 10 天的值会自动切到“d.hh:mm:ss”,但依然受d范围限制,不推荐用于未知长度的间隔
跨日计算要注意 DateTime.Date 截断时分秒
如果只是想算“几个自然日”,比如签到连续天数,不能直接减再取 .Days——因为 2024-01-01 23:59 和 2024-01-02 00:01 相减只有 2 分钟,.Days 是 0。
- 正确做法是先归一化到当天 0 点:
dt1.Date和dt2.Date再相减 - 或者用
(dt2.Date - dt1.Date).Days,确保只比日期部分 - 注意
.Date会保留Kind,如果原值是Utc,.Date还是Utc,不会变成本地时间
真正麻烦的不是怎么算,而是搞清你要的是“物理时间差”还是“日历日差”——前者看 TotalSeconds,后者得先 .Date 再减。很多 bug 都卡在这一步。


















