用 (date2 - date1).Days 可精确获取整数日历天数差,基于 Ticks 计算;跨时区须先统一为 UTC 或使用 DateTimeOffset;注意 DateTime 和 TimeSpan 的边界限制。

用 DateTime.Subtract 或减法运算符最直接
两个 DateTime 相减,C# 会自动返回 TimeSpan,它的 .Days 属性就是整数天数差。这不是近似值,而是基于 Ticks 精确计算的绝对差值(忽略时分秒)。
常见错误是误用 .TotalDays——它返回 double,包含小数部分(比如 1.5 天),而多数业务场景要的是“完整日历天数差”,比如 2024-01-01 到 2024-01-03 是 2 天,不是 2.999…。
- 用
date2.Subtract(date1).Days或更简洁的(date2 - date1).Days - 如果需要向下取整的“经过天数”,
.Days比.TotalDays更安全 - 注意:结果可正可负,取决于谁减谁;若只关心绝对差,套一层
Math.Abs()
跨时区计算必须先统一 DateTimeKind
如果你的两个 DateTime 一个标记为 Utc、一个为 Local,直接相减会出错逻辑——C# 不会自动转换,而是按原始 Ticks 值硬算,结果可能偏差数小时甚至一整天。
典型现象:本地时间 new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Local) 和 UTC 时间 new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) 直接相减,.Days 可能是 0,但实际日历上可能是不同日期。
- 统一转成 UTC:
date.ToUniversalTime()再相减 - 或都转成
DateTimeKind.Unspecified(仅当确认两者本就同属一个时区) - 别依赖
.ToLocalTime()自动处理——它依赖运行环境时区,CI/CD 或 Docker 容器里容易出错
DateTimeOffset 是更健壮的替代方案
当你明确需要保留时区偏移并做跨时区天数计算,DateTimeOffset 比 DateTime 更可靠。它把时间和偏移打包成一个值,相减后 .Days 是基于 UTC 瞬间对齐的真实日历天数差。
例如:东京时间 new DateTimeOffset(2024, 1, 1, 9, 0, 0, TimeSpan.FromHours(9)) 和纽约时间 new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.FromHours(-5)),它们对应同一 UTC 瞬间,相减得 0 天;而用 DateTime 容易误判为跨天。
- 构造时显式传入
TimeSpan偏移,避免依赖系统时区 -
(offset2 - offset1).Days结果和DateTime一样是整数,但语义更清晰 - 兼容性注意:.NET Framework 3.5+ / .NET Core 1.0+ 支持,老项目需确认基线
别忽略 DateTime 的最小/最大值边界
DateTime 有硬性范围:DateTime.MinValue 是 0001-01-01,DateTime.MaxValue 是 9999-12-31。超出会抛 ArgumentOutOfRangeException。但更隐蔽的问题是:两个合法日期相减,结果 TimeSpan 也可能越界——TimeSpan 最大值约 ±10675199 天(约 ±29247 年)。
现象:计算公元元年和公元 30000 年的差,.Days 会溢出,但错误发生在 TimeSpan 构造阶段,报错信息是 TimeSpan too long,不是日期本身非法。
- 提前校验跨度:
Math.Abs((date2 - date1).Ticks) - 如需超长区间,改用
long手动算年/月/日(比如用NodaTime库) - 数据库读出的
datetime字段若为0001-01-01(SQL Server 默认值),和正常日期相减极易触发此边界
事情说清了就结束。真正难的不是算天数,而是搞清楚你手上的两个时间,到底代表什么——是墙上钟表、服务器滴答,还是用户手机里那个带时区的提醒。










