- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
本篇技术指南围绕 Humanizer 的 FluentDate API 中In.Two静态类展开,它提供「从现在起 2 秒/分钟/小时/天/周/月/年」以及「从指定日期起偏移 2 个时间单位」两类共 14 个成员。读者读完本文将掌握In系列数字类的完整成员清单、底层 T4 模板生成原理、DateTime.UtcNow与AddDays/AddMonths/AddYears的实际调用链,以及如何借助仓库测试用例验证行为,从而在自己的 .NET 项目中写出可读性更高、且可预测的日期偏移代码。
一、FluentDate:让日期偏移像说英语一样自然
Humanizer 的核心目标是让字符串、枚举、日期、时间、数字与数量的处理更贴近人类语言习惯。FluentDate 是其中的一个子模块,它把「两个小时后」「三天后」「两年后」这类自然语言表达,直接映射成强类型、可编译、带智能感知的 C# 代码。
In是一个public partial class(定义于 In.cs),它通过多个 partial 声明聚合了一组嵌套静态类:In.One到In.Ten十个数字类,外加月份类(In.January、In.February……)以及TheYear(int year)等方法。十个数字类并非手写,而是由 T4 文本模板 In.SomeTimeFrom.tt 自动生成源码文件 In.SomeTimeFrom.cs,这也解释了为什么One到Ten的结构高度一致——它们本质上是同一套模板循环 10 次的产物。
In.Two正是这套模板在i = 2时的输出,对应「二」这个数量词。其类型定义为:
public static class Two它直接嵌套在In内部,因此完整引用路径是Humanizer.In.Two;In本身继承自System.Object,Two作为嵌套静态类同样如此。由于成员均为public static,调用时无需任何实例化或依赖注入,在项目里引用Humanizer程序集后即可直接使用。
二、In.Two 的七个属性:从当前时刻起偏移 2 个单位
In.Two提供 7 个只读属性,全部返回System.DateTime,语义均为「从现在起 2 个时间单位之后」。下表汇总了文档 Humanizer.In.Two.md 中声明的完整属性清单:
| 属性 | 文档语义 | 返回类型 | 底层实现(见 In.SomeTimeFrom.cs) |
|---|---|---|---|
Seconds | 2 seconds from now(2 秒后) | System.DateTime | DateTime.UtcNow.AddSeconds(2) |
Minutes | 2 minutes from now(2 分钟后) | System.DateTime | DateTime.UtcNow.AddMinutes(2) |
Hours | 2 hours from now(2 小时后) | System.DateTime | DateTime.UtcNow.AddHours(2) |
Days | 2 days from now(2 天后) | System.DateTime | DateTime.UtcNow.AddDays(2) |
Weeks | 2 weeks from now(2 周后) | System.DateTime | DateTime.UtcNow.AddDays(14) |
Months | 2 months from now(2 个月后) | System.DateTime | DateTime.UtcNow.AddMonths(2) |
Years | 2 years from now(2 年后) | System.DateTime | DateTime.UtcNow.AddYears(2) |
一个值得注意的实现细节是Weeks:模板没有调用AddWeeks(.NET 也不存在该方法),而是直接等价为AddDays(2 * 7),即AddDays(14)。这与文档中「2 weeks from now」的语义完全一致,也意味着周是一个固定的 7 天时长概念,不受月份长短或夏令时影响。
示例用法:
using Humanizer; // 从现在起 2 小时后 DateTime reminderTime = In.Two.Hours; // 从现在起 2 周后(即 UtcNow + 14 天) DateTime reviewDue = In.Two.Weeks;三、In.Two 的七个方法:从指定日期起偏移 2 个单位
除属性外,In.Two还提供 7 个同名加From后缀的方法,每个方法接收一个System.DateTime date参数,返回该日期偏移 2 个时间单位后的System.DateTime,语义为「从给定日期起 2 个时间单位之后」。完整方法清单同样来自 Humanizer.In.Two.md:
| 方法签名 | 文档语义 | 底层实现(见 In.SomeTimeFrom.cs) |
|---|---|---|
SecondsFrom(System.DateTime date) | 2 seconds from the provided date | date.AddSeconds(2) |
MinutesFrom(System.DateTime date) | 2 minutes from the provided date | date.AddMinutes(2) |
HoursFrom(System.DateTime date) | 2 hours from the provided date | date.AddHours(2) |
DaysFrom(System.DateTime date) | 2 days from the provided date | date.AddDays(2) |
WeeksFrom(System.DateTime date) | 2 weeks from the provided date | date.AddDays(14) |
MonthsFrom(System.DateTime date) | 2 months from the provided date | date.AddMonths(2) |
YearsFrom(System.DateTime date) | 2 years from the provided date | date.AddYears(2) |
与属性版本最大的区别在于基准点:属性以DateTime.UtcNow为起点,而From方法以调用方传入的date为起点,且不会读写任何全局时钟。这意味着From系列天然具有确定性——同样的输入日期必然得到同样的输出,非常适合在测试和需要可复现计算的场景中使用。
using Humanizer; var startDate = new DateTime(2024, 1, 30); // 从指定日期起 2 个月后:2024 年 3 月 30 日 DateTime dueDate = In.Two.MonthsFrom(startDate); // 从指定日期起 2 年后:2026 年 1 月 30 日 DateTime anniversary = In.Two.YearsFrom(startDate);四、源码级原理:T4 模板如何批量产出 In.One ~ In.Ten
In.Two的代码不是手工维护的,理解其生成机制有助于你把握整个In数字系列的边界。查看 In.SomeTimeFrom.tt 的核心循环:
<#for (var i = 1; i <= 10; i++){ var plural = i > 1 ? "s" : ""; ... #> public static class <#= i.ToWords().Dehumanize() #> { public static DateTime <#= second #> => DateTime.UtcNow.AddSeconds(<#= i #>); ... } <#}#>其中三个关键机制值得展开:
- 类名由
i.ToWords().Dehumanize()计算:模板先是调用 Humanizer 自己的ToWords()扩展把数字i转成英文单词(2→two),再用Dehumanize()把单词转成 PascalCase 标识符(two→Two)。这正是 Humanizer「用自身能力生成自身代码」的体现,也保证了One到Ten的类名与数量词严格对应。 - 单复数由
i > 1决定:模板为每个数字类统一生成Second(s)、Minute(s)、Hour(s)、Day(s)、Week(s)、Month(s)、Year(s)七组属性与方法名。由于Two大于 1,所有成员名均采用复数形式,于是我们看到的是Seconds而非Second,是DaysFrom而非DayFrom(后者是In.One的命名风格)。 - 周统一换算为天数:模板中对周的实现是
AddDays(<#= i * 7 #>),所以In.Two.Weeks生成的是AddDays(14),与前文表格一致;月、年则直接委托AddMonths(i)、AddYears(i)。
另外,In类还通过 In.cs 补充了TheYear(int year)方法(返回指定年份的 1 月 1 日),以及由其他 T4 模板生成的月份类(In.January、In.February等)与Of(year)系列方法。整个 FluentDate 目录还包含InDate(返回DateOnly的对应版本)和On/OnDate(构造特定日期的版本),共同构成一套完整的自然语言日期构造体系。
五、实践验证:测试如何确认 In 系列行为
仓库测试为In.Two的行为提供了直接证据。在 InTests.cs 中,InFiveDays测试展示了From系列的实际调用模式:
[Fact] public void InFiveDays() { var baseDate = On.January.The21st; var date = In.Five.DaysFrom(baseDate); Assert.Equal(baseDate.AddDays(5), date); }把数字换成Two即可得到In.Two.DaysFrom(baseDate) == baseDate.AddDays(2)的等价断言——From方法就是对DateTime.AddXxx的直通封装,没有任何额外逻辑。
更系统的验证来自 GeneratedFluentDateTests.cs。该测试文件内置了一张数量映射表RelativeAmounts:
["One"] = 1, ["Two"] = 2, ["Three"] = 3, ... ["Ten"] = 10并通过反射遍历生成的成员,用Add辅助方法(第 265-287 行)按RelativeDateUnit枚举(Second/Minute/Hour/Day/Week/Month/Year)逐一断言每个属性与方法的结果,其中周同样按AddDays(amount * 7)校验。这意味着In.Two的全部 14 个成员都在生成式测试的覆盖范围内,任何模板改动导致行为偏离都会使测试失败。
六、使用注意事项:UtcNow、日历运算与确定性的取舍
在实战中使用In.Two(乃至整个In数字系列)时,有三点需要留意,这些建议同样来自仓库场景文档 fluent-dates-and-time-spans.mdx:
- 属性基于
DateTime.UtcNow:In.Two.Days等属性在求值瞬间读取协调世界时,因此它们是「相对当前时刻」的动态值。若代码或测试需要可重复结果,应优先使用From方法注入起始日期,或改用它处提供的固定基准。文档明确建议:"Properties withoutFromorOf(year)readDateTime.NoworUtcNow. Avoid them in deterministic code." - 月、年偏移是日历运算而非时长估算:
MonthsFrom/YearsFrom直接继承DateTime.AddMonths/AddYears的规范化语义。例如把 2 月 29 日偏移 2 个月到非闰年月份,会规范化为 4 月末附近的有效日期(AddMonths会做月末截断处理),不会抛异常;这类行为与固定 24 小时一天的TimeSpan运算有本质区别。 - 周始终是 7 天:
In.Two.Weeks等价于AddDays(14),不受当月天数影响;若你的业务周期依赖自然月(如「下下月的今天」),应使用MonthsFrom而非多次DaysFrom。
结合这些约束,推荐的可预测写法是:业务计算全部使用In.Two.XxxFrom(startDate)这类带基准的方法;只有「从现在起 N 个单位」且可接受动态结果的通知、提醒类场景,才使用In.Two.Xxx属性。
七、小结
In.Two是 Humanizer FluentDate 中数量词In.One~In.Ten家族的标准一员,通过 7 个属性(基于UtcNow)与 7 个From方法(基于入参日期)覆盖秒、分、时、天、周、月、年七种时间单位。它的全部成员由 In.SomeTimeFrom.tt 模板生成,语义透明、零依赖,配合 InTests.cs 与 GeneratedFluentDateTests.cs 的测试保障,是写出「人话级」日期代码的可靠基础。若需要DateOnly版本,可继续查阅同一目录下的 InDate.SomeTimeFrom.cs;若要构造「某个具体的 2 号」而非「2 个时间单位之后」,请转向On/OnDate系列。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer FluentDate 详解:InDate.Eight 日期偏移 API 的用法与源码实现
Humanizer FluentDate 详解:InDate.Eight 日期偏移 API 的用法与源码实现 本文以 Humanizer 开源仓库中的 API
开发工具ChatGPT for Google浏览器扩展的插件系统设计终极指南:如何构建模块化AI搜索助手
ChatGPT for Google浏览器扩展的插件系统设计终极指南:如何构建模块化AI搜索助手 ChatGPT for Google是一款创新的浏览器扩展插件
开发工具Humanizer FluentDate API 解析:`In.Three`——用一句"3 个时间单位之后"完成日期计算
Humanizer FluentDate API 解析: In.Three ——用一句"3 个时间单位之后"完成日期计算 In.Three 是 Humanize
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考