☰
Humanizer FluentDate 之 On 类:用自然语言优雅构造日期的流畅 API 完全指南
2026/9/28 3:30:20 网站建设 项目流程
  • 开发工具

【免费下载链接】Humanizer

Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities

项目地址:https://gitcode.com/gh_mirrors/hu/Humanizer
点击查看免费下载

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.January1 月The1st~The31st
On.February2 月The1st~The29th
On.March3 月The1st~The31st
On.April4 月The1st~The30th
On.May5 月The1st~The31st
On.June6 月The1st~The30th
On.July7 月The1st~The31st
On.August8 月The1st~The31st
On.September9 月The1st~The30th
On.October10 月The1st~The31st
On.November11 月The1st~The30th
On.December12 月The1st~The31st

每个嵌套类内包含两组静态成员:

  1. 一个The(int dayNumber)方法:返回当前年份下该月第dayNumber天的DateTime;
  2. 一组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>.TheNthOn.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/ 目录。

边界与注意事项

  1. 年份恒为今年:On所有成员都绑定DateTime.Now.Year,跨年时(如 12 月 31 日 23:59 与 1 月 1 日 00:00 之间)两次调用可能得到不同年份的结果;需要固定年份时请改用In.XxxOf(year)或自行new DateTime(...)。
  2. 2 月 29 日仅在闰年合法:On.February.The29th在平年会抛ArgumentOutOfRangeException,因为模板以 2012 闰年为基准生成到 29 日,而构造时使用的是实际当前年份。业务上应避免无条件调用该属性。
  3. 不存在的日期同样抛异常:On.April.The31st、On.September.The31st等属性根本不存在(不会通过编译),而The(31)传参会在运行时抛异常——静态属性在编译期就替你拦截了这类错误,这也是序数属性的一大价值。
  4. 返回值是DateTime而非DateOnly:On返回带时间的DateTime(时间为 00:00),若只关心日期可改用 .NET 6+ 下的OnDate系列。
  5. 时区基准差异: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

项目地址:https://gitcode.com/gh_mirrors/hu/Humanizer
点击查看免费下载
上一篇:LinkSwift:九大网盘直链下载助手,告别下载限速的智能解决方案
下一篇:8大网盘极速下载:LinkSwift浏览器脚本终极解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询