- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
Humanizer 的InDate系列是专门面向DateOnly的流畅日期构造 API,而InDate.Three则集中提供了以3 个时间单位为步长的日期计算能力:3 天、3 周、3 个月、3 年后(或从任意给定日期起算)的日期。读完本文,你将掌握InDate.Three全部 4 个属性与 8 个方法的签名、返回值与语义差异,理解其基于UtcNow与AddDays/AddMonths/AddYears的实现原理,并能在 .NET 6+ 项目中写出可读、可复现的日期偏移代码。
一、定位:InDate是面向DateOnly的流畅日期构造器
InDate是 Humanizer FluentDate 模块中的一个public partial class,与面向DateTime的In类形成对应关系。从源码结构看,InDate.cs 提供了TheYear(int year)与各月份属性(January、April等,返回当年该月 1 日的DateOnly),而 InDate.SomeTimeFrom.cs 中则定义了从One到Ten共 10 个嵌套静态类,InDate.Three就是其中的第 3 个。
public partial class InDate { public static class Three { // 3 天 / 3 周 / 3 个月 / 3 年后(相对当前时刻) public static DateOnly Days { get; } public static DateOnly Weeks { get; } public static DateOnly Months { get; } public static DateOnly Years { get; } // 从指定日期起算 public static DateOnly DaysFrom(DateOnly date); public static DateOnly DaysFrom(DateTime date); public static DateOnly WeeksFrom(DateOnly date); public static DateOnly WeeksFrom(DateTime date); public static DateOnly MonthsFrom(DateOnly date); public static DateOnly MonthsFrom(DateTime date); public static DateOnly YearsFrom(DateOnly date); public static DateOnly YearsFrom(DateTime date); } }对应 API 文档位于 website/versioned_docs/version-3.0.1/api/Humanizer.InDate.Three.md,整个InDate.Three类的返回类型一律是System.DateOnly,这意味着该 API 仅在 .NET 6 及以上框架可用(源码中整体包裹在#if NET6_0_OR_GREATER预处理器指令内)。
二、属性速览:四个“从现在起算”的只读属性
InDate.Three提供 4 个静态只读属性,语义均为“从现在起 N 个时间单位之后”,内部统一基于DateTime.UtcNow计算:
| 属性 | 文档说明 | 返回类型 | 内部实现(源码) |
|---|---|---|---|
Days | 3 days from now(3 天后) | System.DateOnly | DateOnly.FromDateTime(DateTime.UtcNow.AddDays(3)) |
Weeks | 3 weeks from now(3 周后) | System.DateOnly | DateOnly.FromDateTime(DateTime.UtcNow.AddDays(21)) |
Months | 3 months from now(3 个月后) | System.DateOnly | DateOnly.FromDateTime(DateTime.UtcNow.AddMonths(3)) |
Years | 3 years from now(3 年后) | System.DateOnly | DateOnly.FromDateTime(DateTime.UtcNow.AddYears(3)) |
实现细节对应 InDate.SomeTimeFrom.cs 中Three类的第 169–236 行,例如:
public static DateOnly Days => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(3)); public static DateOnly Weeks => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(21));需要特别注意的三点:
- 周是“7 天制”:
Weeks在源码中等价于AddDays(21)(3 × 7 天),而不是调用AddWeeks,因为DateOnly本身只提供AddDays/AddMonths/AddYears。 - 基准时刻是 UTC:这四个属性读的是
DateTime.UtcNow而非本地DateTime.Now,因此取值随“当前 UTC 时刻”漂移,属于非确定性表达式(详见后文“可复现性”一节)。 - 只取日期部分:无论当前 UTC 时刻几点几分,结果都通过
DateOnly.FromDateTime截断为纯日期,不含时间分量。
三、方法速览:八个“从指定日期起算”的重载
与属性相对,InDate.Three的方法族允许传入一个基准日期,从而得到确定性的偏移结果。每个时间单位都有两个重载:
| 方法 | 参数类型 | 返回类型 | 源码实现 |
|---|---|---|---|
DaysFrom | System.DateOnly | System.DateOnly | date.AddDays(3) |
DaysFrom | System.DateTime | System.DateOnly | DateOnly.FromDateTime(date.AddDays(3)) |
WeeksFrom | System.DateOnly | System.DateOnly | date.AddDays(21) |
WeeksFrom | System.DateTime | System.DateOnly | DateOnly.FromDateTime(date.AddDays(21)) |
MonthsFrom | System.DateOnly | System.DateOnly | date.AddMonths(3) |
MonthsFrom | System.DateTime | System.DateOnly | DateOnly.FromDateTime(date.AddMonths(3)) |
YearsFrom | System.DateOnly | System.DateOnly | date.AddYears(3) |
YearsFrom | System.DateTime | System.DateOnly | DateOnly.FromDateTime(date.AddYears(3)) |
两组重载的差异在于输入:传DateOnly时直接调用DateOnly.AddXxx;传DateTime时先对DateTime做加法、再用DateOnly.FromDateTime转换。两种重载的返回值都是DateOnly,因此无论业务层拿到的是DateTime还是DateOnly,都能统一得到纯日期结果,便于与DateOnly字段、参数、ToString("yyyy-MM-dd")格式化等场景对接。
四、源码级原理:T4 模板批量生成与命名约定
InDate.Three并非手写代码,而是由 T4 文本模板 InDate.SomeTimeFrom.tt 批量生成。模板核心循环如下(节选):
<#for (var i = 1; i <= 10; i++){ var plural = i > 1 ? "s" : ""; var day = "Day" + plural; var week = "Week" + plural; var month = "Month" + plural; var year = "Year" + plural; #> public static class <#= i.ToWords().Dehumanize() #> { public static DateOnly <#= day #> => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(<#= i #>)); ... } <#}#>从中可以读出的三条生成规则:
- 类名由数字的英文单词生成:
i.ToWords()将3转为"three",再经Dehumanize()得到帕斯卡命名Three,因此One~Ten十个类的类名完全一致对称。 - 成员名的单复数约定:
i > 1时成员名为复数(Days/Weeks/Months/Years),i == 1时为单数(Day/Week/Month/Year)。也就是说,InDate.One.Day与InDate.Three.Days的命名不对称,这是刻意设计,使用时不要写错。 - 周统一用天数展开:模板中
week成员的实现是AddDays(i * 7),与属性部分的UtcNow.AddDays(i * 7)一致。
同样的模式也存在于面向DateTime的In类(见 In.SomeTimeFrom.tt),二者共同构成了 Humanizer FluentDate 的“数字 + 时间单位”命名空间。
五、实战用法:可复现的日期偏移
InDate.Three最典型的用法是传入确定基准、获得确定结果,适合定时任务排期、订阅周期计算、报表截止日期等场景。
using Humanizer; // 从固定基准日偏移(确定性,可单测) var baseDate = new DateOnly(2026, 9, 27); var in3Days = InDate.Three.DaysFrom(baseDate); // 2026-09-30 var in3Weeks = InDate.Three.WeeksFrom(baseDate); // 2026-10-18 var in3Months = InDate.Three.MonthsFrom(baseDate); // 2026-12-27 var in3Years = InDate.Three.YearsFrom(baseDate); // 2029-09-27 // DateTime 重载同样返回 DateOnly var fromDateTime = InDate.Three.MonthsFrom(new DateTime(2026, 9, 27, 14, 30, 0)); // 结果仍是 DateOnly: 2026-12-27如果只是想表达“相对现在的 3 个月后”,也可以直接用属性:
var reminderDate = InDate.Three.Months; // ≈ UtcNow 加 3 个月后的日期Humanizer 官方场景示例 fluent-dates-and-time-spans.mdx 与可运行示例 Program.cs 也演示了这类日历型日期运算与时长型运算(如1.5.Days()返回TimeSpan)的区分思路。
测试验证
仓库测试 InDateTests.cs 对同族 API 的验证方式可以直接套用到InDate.Three:
[Fact] public void InFiveDays() { var baseDate = OnDate.January.The21st; var date = InDate.Five.DaysFrom(baseDate); Assert.Equal(baseDate.AddDays(5), date); }即:给定基准日期调用From方法,应与基准日期直接调用AddDays/AddMonths/AddYears的结果完全相等。这也反向印证了InDate.Three.DaysFrom(date)就是date.AddDays(3)的薄封装。
六、实践要点与注意事项
- 框架前提:
DateOnly自 .NET 6 引入,InDate全家(包括InDate.Three)仅在NET6_0_OR_GREATER条件下编译。面向 .NET Framework / .NET 5 及以下时,应改用面向DateTime的In类。 - 确定性优先:
Days/Months/Weeks/Years四个属性依赖UtcNow,每次调用结果随时间变化;需要可重复执行的代码(定时任务、测试、幂等计算)应改为InDate.Three.MonthsFrom(fixedDate)形式。 - 日历算术而非时长算术:
MonthsFrom/YearsFrom委托给AddMonths/AddYears,遵循日历归一化规则——例如从 2 月 29 日加 3 个月到非闰年,结果为 2 月 28 日(5 月 29 日→实际取 5 月末归一),这与TimeSpan的固定 24 小时天数语义有本质区别。 - 命名坑位提醒:
InDate.One使用单数Day/Week/Month/Year,InDate.Three至InDate.Ten使用复数,复制粘贴代码时留意区分。
七、延伸阅读
InDate全部成员(月份属性与Of(int year)方法):Humanizer.InDate API- 面向
DateTime的对应实现:In类(In.SomeTimeFrom.tt)与InAPI 文档 - FluentDate 场景指南:fluent-dates-and-time-spans.mdx
- 生成
InDate.One~Ten的 T4 模板:InDate.SomeTimeFrom.tt,生成结果见 InDate.SomeTimeFrom.cs
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 的 InDate.Three 类完全指南:用 DateOnly 简洁表达"三天/三周/三个月/三年后"
Humanizer 的 InDate.Three 类完全指南:用 DateOnly 简洁表达"三天/三周/三个月/三年后" Humanizer 是 .NET 生
开发工具Humanizer InDate.Two 指南:用流式 API 计算 2 天/周/月/年后的 DateOnly 日期
Humanizer InDate.Two 指南:用流式 API 计算 2 天/周/月/年后的 DateOnly 日期 Humanizer 的 InDate.Tw
开发工具Humanizer InDate.Eight 完全指南:用流式 API 计算 8 天、8 周、8 月、8 年后的 DateOnly 日期
Humanizer InDate.Eight 完全指南:用流式 API 计算 8 天、8 周、8 月、8 年后的 DateOnly 日期 本篇技术指南聚焦 Hu
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考