- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
Humanizer 的On类是 FluentDate(流畅日期)体系的核心入口之一,它把「某年某月某日」这种日期构造需求封装成接近自然语言的调用形式:On.January.The23rd、On.February.The(11)。本文以 Humanizer.On API 文档 为骨架,结合 On.Days.cs 源码、T4 生成模板与单元测试,完整讲解On类的 12 个月份嵌套类、The(int)方法与The1st~The31st属性集合的使用方法、实现原理和边界约束,并顺带梳理它和In、OnDate系列的定位差异。读完你可以在自己的 .NET 项目中直接使用这套 API 写出可读性极强的日期代码。
On 类是什么
在 Humanizer 的文档体系中,On是一个公开类(public class On),继承自System.Object:
public class On它位于Humanizer命名空间下(源码中的实际声明见 On.Days.cs),职责是为每个月份提供流畅的日期访问器(fluent date accessors)。所谓流畅,是指代码读起来像一句英文自然语言:On.January.The21st读作 "on January the 21st",直接表达「在 1 月 21 日」,无需再像new DateTime(year, 1, 21)那样先准备年份变量。
On类本身不直接提供日期成员,它的全部能力由 12 个嵌套类承载,每个嵌套类对应一个月份。在 version-3.0.1 API 文档 中,On只给出类声明,成员明细被拆分到按月份命名的独立页面(例如 Humanizer.On.January.md),这正是 Humanizer 自动生成 API 参考的一贯组织方式。
12 个月份嵌套类:API 全貌
On类内部定义了 12 个嵌套类,名称即月份英文名,全部为public class,且均可直接以On.<Month>形式访问:
| 嵌套类 | 对应月份 | 提供的最多日属性 |
|---|---|---|
On.January | 1 月 | The1st~The31st |
On.February | 2 月 | The1st~The29th |
On.March | 3 月 | The1st~The31st |
On.April | 4 月 | The1st~The30th |
On.May | 5 月 | The1st~The31st |
On.June | 6 月 | The1st~The30th |
On.July | 7 月 | The1st~The31st |
On.August | 8 月 | The1st~The31st |
On.September | 9 月 | The1st~The30th |
On.October | 10 月 | The1st~The31st |
On.November | 11 月 | The1st~The30th |
On.December | 12 月 | The1st~The31st |
每个嵌套类内包含两组静态成员:
- 一个
The(int dayNumber)方法:返回当前年份下该月第dayNumber天的DateTime; - 一组
The1st~The31st(或到该月最大天数)的静态属性:每个属性固定返回当前年份下该月对应日期的DateTime。
以 On.January 文档 中的成员为例,On.January的完整签名形态是:
// The nth day of January of the current year public static DateTime The(int dayNumber); // The 1st day of January of the current year public static DateTime The1st { get; } // ... 依此类推直至 The31st public static DateTime The31st { get; }所有成员返回值类型均为System.DateTime,且全部是静态成员,无需实例化On或月份嵌套类。
核心成员详解:The 方法与序号属性
The(int dayNumber):按天数动态取值
The(int dayNumber)是每个月份类都有的通用入口,接受一个int参数,表示当月第几天:
public static DateTime The(int dayNumber) => new(DateTime.Now.Year, 1, dayNumber); // 以 January 为例从 On.Days.cs 源码 可以看到,它直接以「当前年份 + 固定月份 + 传入天数」构造DateTime。使用示例:
var date = On.February.The(11); // 当年 2 月 11 日 var date2 = On.October.The(31); // 当年 10 月 31 日dayNumber需要落在该月的合法天数范围内(例如On.February.The(30)会因 2 月无 30 日而抛出ArgumentOutOfRangeException),这一点与直接new DateTime(year, month, day)的约束完全一致。
The1st~The31st:固定序数日属性
除了动态方法,每个月份类还预生成了一组按序数词命名的只读属性。以January为例(On.Days.cs):
public static DateTime The1st => new(DateTime.Now.Year, 1, 1);属性名使用英文序数词后缀:1st、2nd、3rd、4th……直到该月最大天数。这样写业务代码时几乎零心智负担:
var newYear = On.January.The1st; // 当年 1 月 1 日 var valentine = On.February.The14th; // 当年 2 月 14 日 var halloween = On.October.The31st; // 当年 10 月 31 日统一的「当前年份」语义
注意:无论是The(int)还是TheNth属性,年份一律取自DateTime.Now.Year(本地当前时间),即所有On成员返回的都是「今年」的日期。如果需要指定具体年份,Humanizer 的In类提供了对应的XxxOf(int year)方法(详见下文「与 In 类的关系」小节)。
源码原理:T4 模板批量生成
On类巨大的成员量(12 个嵌套类 × 每个类 20~32 个成员)并非手写维护,而是由 T4 文本模板生成的。On.Days.tt 中清晰地体现了生成逻辑,核心循环如下(节选):
for (var month = 1; month <= 12; month++) { var firstDayOfMonth = new DateTime(leapYear, month, 1); var monthName = firstDayOfMonth.ToString("MMMM"); public class <#= monthName #> // 生成 12 个月份嵌套类 { public static DateTime The(int dayNumber) => new(DateTime.Now.Year, <#= month #>, dayNumber); for (var day = 1; day <= DateTime.DaysInMonth(leapYear, month); day++) { var ordinalDay = day.Ordinalize(); public static DateTime The<#= ordinalDay #> => new(DateTime.Now.Year, <#= month #>, <#= day #>); } } }这里有几个值得注意的实现细节:
- 闰年基准(
leapYear = 2012):模板用 2012 年(闰年)的DateTime.DaysInMonth决定每个月份生成多少个日属性,因此 2 月会生成到The29th(而普通年份 2 月只有 28 天,此时On.February.The29th会抛出异常,这一点在「边界与注意事项」中还会强调)。 Ordinalize()的复用:序数属性名(The1st、The22nd、The23rd)直接调用 Humanizer 自身的 OrdinalizeExtensions.cs 扩展方法生成,体现了库内部「自举」的设计——用 Humanizer 生成 Humanizer 的 API。- 月份名取自
ToString("MMMM"):模板用文化相关的完整月份名(January、February……)作为嵌套类名,保证了类名与月份语义一一对应。
最终生成的 On.Days.cs 约 2300 行,全部由模板产出,人工只维护模板本身。
日期类型扩展:OnDate 与 DateOnly 变体
On类返回的是System.DateTime。如果项目面向 .NET 6+ 且希望使用DateOnly(仅日期、无时间的轻量类型),Humanizer 提供了对偶类OnDate,源码位于 OnDate.Days.cs(该文件用#if NET6_0_OR_GREATER条件编译保护):
public class OnDate { public class January { // The nth day of January of the current year public static DateOnly The(int dayNumber) => new(DateTime.Now.Year, 1, dayNumber); // The 1st day of January of the current year public static DateOnly The1st => new(DateTime.Now.Year, 1, 1); } }OnDate的嵌套结构与On完全一致(同样有 12 个月份类、The(int)与TheNth属性),唯一差异是返回值类型为DateOnly。可以据此推断:这套流畅 API 在设计上刻意保持了「按日期类型分族」的策略——DateTime用On/In,DateOnly用OnDate/InDate,方便不同类型项目各取所需。对应的InDate系列位于 InDate.cs 与 InDate.Months.cs。
与 In 类的关系:On 管「某日」,In 管「某月首日」
On并非孤立存在,它与In类共同构成 FluentDate 的「定点日期」能力,二者常配合使用:
| 需求 | 推荐 API | 示例 |
|---|---|---|
| 今年某月的某一天 | On.<Month>.TheNth | On.December.The25th |
| 指定年份某月的某一天 | On.<Month>.The(n)配合DateTime构造 | 见下方进阶示例 |
| 今年某月 1 日 | In.<Month> | In.April |
| 指定年份某月 1 日 | In.<Month>Of(year) | In.AprilOf(2030) |
| 某年 1 月 1 日(元旦) | In.TheYear(year) | In.TheYear(2030) |
In类的月份属性与Of(year)方法定义在 In.Months.cs,例如:
// Returns 1st of April of the current year public static DateTime April => new(DateTime.UtcNow.Year, 4, 1); // Returns 1st of April of the year passed in public static DateTime AprilOf(int year) => new(year, 4, 1);In的「当前年份」取的是DateTime.UtcNow.Year,而On取的是DateTime.Now.Year,二者时钟基准不同(UTC vs 本地时间),在跨时区场景需要留意。In.cs 还提供了TheYear(int year)方法,返回指定年份的 1 月 1 日:
public static DateTime TheYear(int year) => new(year, 1, 1);另外,In类还支持「从当前时刻/指定时刻起算 N 个时间单位之后」的语义(In.SomeTimeFrom.cs),例如In.One.Week(一周后)、In.Two.Hours(两小时后),与On的「绝对日期」定位形成互补。
实战示例:完整可运行代码
结合上述 API,下面是一段完整的实战示例,演示On类在日常业务中的典型用法(假定当前运行年份为 2026 年):
using Humanizer; // 1) 用序数属性取「今年」的固定日期 DateTime christmas = On.December.The25th; // 2026-12-25 DateTime piDay = On.March.The14th; // 2026-03-14 DateTime newYear = On.January.The1st; // 2026-01-01 // 2) 用 The(int) 动态取「今年」任意一天 DateTime payday = On.MonthlyPayday(15); // 假设某月 15 日,见下方辅助方法 DateTime feb11 = On.February.The(11); // 2026-02-11 // 3) 与 In 类配合,构造「指定年份的特定日期」 DateTime independenceDay2030 = new( In.TheYear(2030).Year, // 2030 年 1 月 1 日,取其年份 On.July.The4th.Month, // 7 月 On.July.The4th.Day); // 4 日 // 结果:2030-07-04 // 4) 直接比较日期 bool isTodayPayday = On.December.The25th == DateTime.Today; // 5) 打印更人性化的描述 Console.WriteLine(christmas.Humanize()); // 例如 "December 25"说明:上述第 2 步中的
On.MonthlyPayday(15)是示意性辅助方法,实际库中不存在;On类只提供按月/日定位的成员。若要表达「每月某日」,通常按具体月份调用,如On.January.The(15)。
代码中出现的.Humanize()、.Ordinalize()等扩展均来自 Humanizer 库本体,可见 FluentDate 与字符串人性化能力是同一套 API 生态。
测试验证:仓库中的真实用例
On类的行为有单元测试直接背书,见 tests/Humanizer.Tests/FluentDate/OnTests.cs:
public class OnTests { [Fact] public void OnJanuaryThe23rd() => Assert.Equal(new(DateTime.Now.Year, 1, 23), On.January.The23rd); [Fact] public void OnDecemberThe4th() => Assert.Equal(new(DateTime.Now.Year, 12, 4), On.December.The4th); [Fact] public void OnFebruaryThe() => Assert.Equal(new(DateTime.Now.Year, 2, 11), On.February.The(11)); }这三个测试用例恰好覆盖了三种访问形态:The23rd序数属性、The4th序数属性、以及The(11)动态方法,并确认了「年份取DateTime.Now.Year」的行为。此外 InTests.cs 中也有On.January.The21st作为基准日期参与In类相对时间的断言(InTests.cs),说明两个类在真实测试中经常组合使用。FluentDate 的其余测试覆盖见 tests/Humanizer.Tests/FluentDate/ 目录。
边界与注意事项
- 年份恒为今年:
On所有成员都绑定DateTime.Now.Year,跨年时(如 12 月 31 日 23:59 与 1 月 1 日 00:00 之间)两次调用可能得到不同年份的结果;需要固定年份时请改用In.XxxOf(year)或自行new DateTime(...)。 - 2 月 29 日仅在闰年合法:
On.February.The29th在平年会抛ArgumentOutOfRangeException,因为模板以 2012 闰年为基准生成到 29 日,而构造时使用的是实际当前年份。业务上应避免无条件调用该属性。 - 不存在的日期同样抛异常:
On.April.The31st、On.September.The31st等属性根本不存在(不会通过编译),而The(31)传参会在运行时抛异常——静态属性在编译期就替你拦截了这类错误,这也是序数属性的一大价值。 - 返回值是
DateTime而非DateOnly:On返回带时间的DateTime(时间为 00:00),若只关心日期可改用 .NET 6+ 下的OnDate系列。 - 时区基准差异:
On/OnDate用DateTime.Now.Year(本地时间),In的月份属性用DateTime.UtcNow.Year(UTC),分布式系统中需显式统一时钟来源。
相关资源
- API 参考:Humanizer.On.md、Humanizer.On.January.md
- 源码实现:src/Humanizer/FluentDate/On.Days.cs、T4 模板 src/Humanizer/FluentDate/On.Days.tt
- 关联类型:OnDate.Days.cs、In.cs、In.Months.cs、In.SomeTimeFrom.cs
- 单元测试:tests/Humanizer.Tests/FluentDate/OnTests.cs
- 可运行的官方示例工程:website/docs/_examples/scenarios-fluent-dates/
如果你正在写日历、排期、账单日等强日期业务,把new DateTime(year, month, day)换成On.<Month>.TheNth,代码的可读性会立刻上一个台阶——这就是 Humanizer FluentDate 设计的初衷。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer FluentDate 之 On 类:以自然语言流畅构造"当月日期"的 DateTime 完整指南
Humanizer FluentDate 之 On 类:以自然语言流畅构造"当月日期"的 DateTime 完整指南 本文是 Humanizer 2.10.1
开发工具Humanizer On 类 API 详解:用 FluentDate 流畅构造当年日期的完整指南
Humanizer On 类 API 详解:用 FluentDate 流畅构造当年日期的完整指南 本篇技术指南以 Humanizer 2.11.10 版本 AP
开发工具Humanizer 的 On.February 流畅日期 API:用自然语言构造二月日期的完整指南
Humanizer 的 On.February 流畅日期 API:用自然语言构造二月日期的完整指南 导读 On.February 是 .NET 字符串与日期处理
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考