- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
Humanizer 的On静态类为 .NET 开发者提供了一套"口语化"的流式日期访问器(fluent date accessors),让你可以用On.August.The23rd这样接近自然语言的表达式直接获得"当前年份的 8 月 23 日"。本篇以On.August类为切入点,逐一解析其全部 31 个静态属性与The(int)方法,并结合源码揭示其由 T4 模板自动生成的实现机制、On与OnDate两个入口的差异,以及实际使用中的注意事项,帮助你彻底掌握 Humanizer 流式日期 API 的正确用法。
一、类概览:On.August 是什么
On.August是 On 类 内部嵌套的一个公开类,其唯一职责是提供 8 月(August)的流式日期访问器。官方 API 文档(Humanizer.On.August.md)中给出的类声明为:
public class On.August该类直接继承自System.Object,本身不携带任何状态,全部成员均为public static,因此使用时不需要实例化,直接通过On.August.xxx静态访问即可。类的官方摘要为:"Provides fluent date accessors for August",即"为 8 月提供流式日期访问器"。
从源码角度看,On是一个容器类,内部为一年 12 个月各嵌套了一个同名字类(January、February、…、December),August只是其中之一。完整的生成代码位于 On.Days.cs(约第 1370 行开始是August类的定义)。
二、31 个静态属性:The1st 到 The31st 完整清单
On.August的核心成员是 31 个静态只读属性,分别对应 8 月的第 1 天到第 31 天(8 月为大月,共 31 天)。每个属性的声明形态完全一致,例如文档中的The10th声明为:
public static System.DateTime The10th { get; }全部 31 个属性及含义如下(属性值均为System.DateTime,年份取自当前年):
| 属性 | 含义 | 属性 | 含义 |
|---|---|---|---|
The1st | 当年 8 月 1 日 | The17th | 当年 8 月 17 日 |
The2nd | 当年 8 月 2 日 | The18th | 当年 8 月 18 日 |
The3rd | 当年 8 月 3 日 | The19th | 当年 8 月 19 日 |
The4th | 当年 8 月 4 日 | The20th | 当年 8 月 20 日 |
The5th | 当年 8 月 5 日 | The21st | 当年 8 月 21 日 |
The6th | 当年 8 月 6 日 | The22nd | 当年 8 月 22 日 |
The7th | 当年 8 月 7 日 | The23rd | 当年 8 月 23 日 |
The8th | 当年 8 月 8 日 | The24th | 当年 8 月 24 日 |
The9th | 当年 8 月 9 日 | The25th | 当年 8 月 25 日 |
The10th | 当年 8 月 10 日 | The26th | 当年 8 月 26 日 |
The11th | 当年 8 月 11 日 | The27th | 当年 8 月 27 日 |
The12th | 当年 8 月 12 日 | The28th | 当年 8 月 28 日 |
The13th | 当年 8 月 13 日 | The29th | 当年 8 月 29 日 |
The14th | 当年 8 月 14 日 | The30th | 当年 8 月 30 日 |
The15th | 当年 8 月 15 日 | The31st | 当年 8 月 31 日 |
The16th | 当年 8 月 16 日 |
属性命名遵循英文序数词规则:1 为st、2 为nd、3 为rd,其余为th(11、12、13 也遵循th,8 月不涉及这些特例)。每个属性的值都精确指向"当前年份"的 8 月对应日期,这一点非常关键:它永远返回的是DateTime.Now.Year这一年的 8 月某天,而不是固定年份。
源码实现印证
On.Days.cs 中August类开头的实现为:
public class August { /// <summary> /// The nth day of August of the current year /// </summary> public static DateTime The(int dayNumber) => new(DateTime.Now.Year, 8, dayNumber); /// <summary> /// The 1st day of August of the current year /// </summary> public static DateTime The1st => new(DateTime.Now.Year, 8, 1); // ... The2nd ~ The31st 以此类推 }可见每个属性本质上都是new DateTime(DateTime.Now.Year, 8, dayNumber)的语法糖封装,属性的 Ordinalize 后缀只是为了让调用点读起来更像人话。测试用例 OnTests.cs 验证了这一行为模式(以其他月份为例):
[Fact] public void OnJanuaryThe23rd() => Assert.Equal(new(DateTime.Now.Year, 1, 23), On.January.The23rd); [Fact] public void OnFebruaryThe() => Assert.Equal(new(DateTime.Now.Year, 2, 11), On.February.The(11));测试确认了"属性/方法返回值 = 当年 + 指定月份 + 指定日"这一等式。
三、动态方法:The(int dayNumber)
除 31 个固定属性外,On.August还提供一个参数化方法,用于获取 8 月任意一天:
public static System.DateTime The(int dayNumber);- 参数
dayNumber:System.Int32类型,表示 8 月内的天数(1 到 31)。 - 返回值:
System.DateTime,即当年 8 月dayNumber日的零点时刻。
使用示例:
DateTime d1 = On.August.The(1); // 当年 8 月 1 日 DateTime d2 = On.August.The(15); // 当年 8 月 15 日 DateTime d3 = On.August.The(31); // 当年 8 月 31 日The(int)与The1st~The31st固定属性的关系是:固定属性是The(int)对 1~31 各参数值的预生成特化。二者底层实现完全一致,都走new(DateTime.Now.Year, 8, dayNumber)这条路径,差别只在于调用点的可读性——On.August.The23rd比On.August.The(23)更接近自然语言。当日期在循环、配置驱动等动态场景中才能确定时,应使用The(int);当日期是写死的常量时,优先使用固定属性以获得最佳可读性。
需要提醒的是,如果传入超出 1~31 范围的值(如The(0)或The(32)),DateTime构造函数会抛出ArgumentOutOfRangeException,因为 8 月没有对应的日期。使用动态方法时应对输入做合法性校验。
四、实现原理:T4 模板自动生成
On.August及另外 11 个月份类并非手写代码,而是由 T4 文本模板(Text Template Transformation Toolkit)批量生成的。生成模板位于 On.Days.tt,其核心逻辑为:
const int leapYear = 2012; for (var month = 1; month <= 12; month++) { var firstDayOfMonth = new DateTime(leapYear, month, 1); var monthName = firstDayOfMonth.ToString("MMMM"); // 输出 public class <monthName> { ... } 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>); } }这段模板揭示了三个重要事实:
- 年份基准采用 2012 闰年:模板以 2012 年作为"标尺"来计算每个月的天数,因为 2012 是闰年,能覆盖 2 月 29 日这一极端情况。月份名通过
new DateTime(2012, month, 1).ToString("MMMM")获得本地化英文名,从而生成January~December12 个嵌套类。 - 天数由
DateTime.DaysInMonth决定:每个类生成多少个TheNth属性,取决于该月在 2012 年有多少天。8 月有 31 天,因此August恰好生成 31 个属性;而 2 月因闰年会生成 29 个属性。 - 序数后缀由
Ordinalize()产生:属性名中的1st/2nd/3rd/th后缀正是 Humanizer 自身的 OrdinalizeExtensions 能力在生成期内的复用——库用它生成自己的 API,属于"自举"(dogfooding)设计。
这意味着On.August的完整形态(31 属性 + 1 方法)全部由模板在构建期展开,最终产物是 On.Days.cs 中约 2345 行代码的一部分,开发者无需手工维护每个日期访问器。
五、On 与 OnDate:DateTime 与 DateOnly 双版本
除了返回System.DateTime的On系列,Humanizer 还提供了面向System.DateOnly的OnDate系列(OnDate.August、OnDate.August.The1st等),其定义在 OnDate.Days.cs:
public class August { public static DateOnly The(int dayNumber) => new(DateTime.Now.Year, 8, dayNumber); public static DateOnly The1st => new(DateTime.Now.Year, 8, 1); // ... The2nd ~ The31st }OnDate系列的特点与使用建议:
- 仅在 .NET 6 及以上目标框架可用。生成模板 OnDate.Days.tt 开头使用
#if NET6_0_OR_GREATER指令包裹,低于该版本的编译目标不会包含OnDate类型。 - 返回值类型为
DateOnly(纯日期、无时间分量),而On系列返回DateTime(含零点时间分量)。如果你的业务只需要"某年某月某日"这一概念(如生日、纪念日、排期),优先用OnDate以避免无意义的时间分量;如果需要与DateTime.Now直接比较或参与时间运算,则用On。 - 两者的月份类结构、属性命名、生成方式完全对称,
OnDate.August同样拥有The1st~The31st与The(int)全部成员。
六、实战使用示例
6.1 获取指定日期
using Humanizer; // 当年 8 月 23 日(属性方式,可读性最佳) DateTime meetingDay = On.August.The23rd; // 当年 8 月 1 日与 8 月 31 日 DateTime monthStart = On.August.The1st; DateTime monthEnd = On.August.The31st; // 动态天数(变量方式) int targetDay = 15; DateTime dynamicDay = On.August.The(targetDay); // .NET 6+ 使用 DateOnly 版本 DateOnly anniversary = OnDate.August.The23rd;6.2 计算距离某个 8 月日期的剩余天数
TimeSpan remaining = On.August.The31st - DateTime.Today; Console.WriteLine($"距当年 8 月 31 日还有 {remaining.Days} 天");6.3 与 Humanizer 其他 API 组合使用
On.August返回的DateTime可以无缝接入 Humanizer 的其他扩展,例如用 DateHumanizeExtensions 做相对时间描述:
Console.WriteLine(On.August.The1st.Humanize()); // 例如 "3 days from now" Console.WriteLine(On.August.The1st.Ordinalize()); // 例如 "August 1st"七、使用注意事项
- 年份是动态的:
On.August.The23rd返回的是"当前年份"的 8 月 23 日,其年份取自DateTime.Now.Year。跨年运行时(如程序跨越 12 月 31 日持续运行),两次调用的返回值年份可能不同,这一点在缓存计算结果、日志落库等场景中要特别留意。 - 返回时刻为当天零点:
On系列返回的DateTime时间分量为00:00:00,即当天的开始时刻;需要当天其他时刻时请在此基础上自行AddHours等操作。 - 参数范围校验:
The(int dayNumber)仅接受 1~31 的合法天数,越界会抛出ArgumentOutOfRangeException。 OnDate的框架门槛:OnDate系列仅在NET6_0_OR_GREATER目标下编译产出,若项目目标框架低于 .NET 6,请使用On系列。- 可读性与动态性取舍:固定属性(
The23rd)适合字面量日期,The(int)适合动态计算出的日期,二者返回值语义完全等价。
八、相关资源导航
- API 参考文档:Humanizer.On.August.md
- 生成源码:On.Days.cs(
August类自第 1370 行起) - 生成模板:On.Days.tt
- DateOnly 版本生成源码:OnDate.Days.cs
- DateOnly 版本生成模板:OnDate.Days.tt
- 单元测试:OnTests.cs 与 OnDateTests.cs
- 其他月份类:
On.January~On.December同位于 On.Days.cs,结构与On.August完全一致,仅月份参数与天数不同。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 流式日期 API 详解:On.August 类的用法、源码实现与测试验证
Humanizer 流式日期 API 详解:On.August 类的用法、源码实现与测试验证 Humanizer 为 .NET 开发者提供了名为 "Fluent
开发工具Humanizer 流式日期 API 详解:In.Two 类在 C 中的用法与实现原理
Humanizer 流式日期 API 详解:In.Two 类在 C 中的用法与实现原理 导读 In.Two 是 Humanizer 流式日期(FluentDat
开发工具Humanizer 流式日期 API 详解:InDate.Six 类的属性、方法与实现原理
Humanizer 流式日期 API 详解:InDate.Six 类的属性、方法与实现原理 本文以 Humanizer 2.13.14 版本文档 InDate.
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考