- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
Humanizer 的 FluentDate 子模块为 .NET 开发者提供了一套"自然语言式"的日期构建 API。本文聚焦其中的OnDate类:它通过嵌套的月份类型与The(int)、The1st~The31st等一系列静态成员,让你在 .NET 6+ 项目中以近乎口语的方式直接构造DateOnly实例。读完本文,你将掌握OnDate的完整成员构成、它与On/In/InDate的关系、底层代码生成原理,以及如何在测试与业务代码中正确使用这套流式 API。
OnDate 类概述
OnDate是 Humanizer 中负责"用流式语法表达某个日期"的公开类,定义如下:
public class OnDate它继承自System.Object,位于Humanizer命名空间下,因此只要项目引用了 Humanizer 包,即可直接使用OnDate.January.The23rd这样的写法。该类的全部实现位于 src/Humanizer/FluentDate/OnDate.Days.cs,并且整个文件被#if NET6_0_OR_GREATER条件编译指令包裹——这意味着它依赖 .NET 6 引入的System.DateOnly类型,仅在 .NET 6 及以上目标框架(TFM)中可用。
从类结构看,OnDate本身是一个纯静态外观(facade)容器,其真正的访问能力来自嵌套其中的 12 个月份类型:January、February、March、April、May、June、July、August、September、October、November、December。每个月份类型都对应"当前年份中该月的某一天"这一语义。
月份嵌套类型与两种访问方式
以 OnDate.Days.cs 中的January为例,可以看到每个月份类型都提供两类静态成员:
public class January { /// <summary> /// The nth day of January of the current year /// </summary> public static DateOnly The(int dayNumber) => new(DateTime.Now.Year, 1, dayNumber); /// <summary> /// The 1st day of January of the current year /// </summary> public static DateOnly The1st => new(DateTime.Now.Year, 1, 1); // The2nd ... The31st 依此类推 }1. 动态方法:The(int dayNumber)
- 签名:
public static DateOnly The(int dayNumber) - 语义:返回当前年份中该月的第
dayNumber天; - 取值范围:1 到该月实际天数。传入超出范围的值会由
new DateTime(...)的构造逻辑抛出ArgumentOutOfRangeException(如 2 月传入 31); - 典型用途:当日期数字来自变量、循环或用户输入时使用,例如
OnDate.February.The(11)。
2. 静态属性:The1st~The31st
- 语义:为每月的每一天生成一个命名属性,如
The1st、The2nd……The31st; - 覆盖范围:按月实际天数生成。1 月、3 月等大月生成到
The31st,2 月仅生成到The28th(模板基于闰年 2012 计算DateTime.DaysInMonth,平年 2 月 29 日通过The(int)才能表达); - 命名风格:采用英文序数词格式(
1st、2nd、3rd、4th…),与Humanizer.Ordinalize()扩展的输出一致; - 典型用途:表达固定不变的"纪念日"式日期,如
OnDate.December.The25th表示"今年圣诞节"。
源码级实现:T4 模板生成与 DateOnly 语义
OnDate.Days.cs并非手写维护的 2300 多行代码,而是由 T4 文本模板 src/Humanizer/FluentDate/OnDate.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"); // 生成月份嵌套类型 for (var day = 1; day <= DateTime.DaysInMonth(leapYear, month); day++) { var ordinalDay = day.Ordinalize(); // 生成 TheNth 属性 } }这段模板说明了三件事:
- 月份命名:借助
DateTime.ToString("MMMM")输出英文月份名(January…December),因此月份类型名与当前区域性设置相关; - 天数上限:以 2012 这个闰年为基准调用
DateTime.DaysInMonth,保证 2 月 29 日(The29th)能被生成出来,而平年 2 月只有 28 天; - 序数词生成:
day.Ordinalize()复用了 Humanizer 自身的序数词扩展,与The1st/The2nd的命名保持一致。
每个访问器最终都收敛到一行构造代码:
=> new(DateTime.Now.Year, <month>, <day>);注意这里显式使用了DateTime.Now.Year,即"当前年份",这是OnDate与下面将要对比的On系列共同的核心语义:它们永远指向"今年"的某一天,而不是任意年份。
与 On、In、InDate 的关系:一套 API 的四种形态
FluentDate 模块围绕"用英文介词表达时间"这一设计,提供了四个相互对照的入口类,全部位于 src/Humanizer/FluentDate 目录:
| 类 | 返回类型 | 语义 | 源码文件 |
|---|---|---|---|
On | DateTime | 今年某月某日(可变日期时间) | On.Days.cs |
OnDate | DateOnly | 今年某月某日(仅日期) | OnDate.Days.cs |
In | DateTime | 今年某月第一天等 | In.Months.cs、In.SomeTimeFrom.cs |
InDate | DateOnly | 今年某月第一天等 | InDate.Months.cs、InDate.cs |
OnvsOnDate:两者成员结构完全一致(On也有January.The(int)、January.The1st等),唯一差别是返回类型——On返回DateTime(带时间部分,时间默认为零点),OnDate返回DateOnly。On的实现位于 On.Days.cs,同样由 On.Days.tt 生成,但不限定 .NET 6;InvsInDate:In/InDate语义为"某月(的第一天)",例如In.April表示今年 4 月 1 日;InDate同样仅在NET6_0_OR_GREATER下编译(见 InDate.Months.cs)。OnDate与InDate的区别可概括为:OnDate精确到"日",InDate只到"月的第一天"。
由此可以总结选型建议:目标框架为 .NET 6+、且只需要"日期"概念时优先用OnDate/InDate;需要与DateTimeAPI 混用、或目标框架为 .NET Framework/.NET Core 3.1 时,使用On/In。
实战示例:从构造到断言
基础用法
using Humanizer; // 今年 1 月 23 日(DateOnly) DateOnly jan23 = OnDate.January.The23rd; // 今年 2 月 11 日(动态天数) DateOnly feb11 = OnDate.February.The(11); // 今年 12 月 25 日 DateOnly christmas = OnDate.December.The25th; // 与 InDate 对照:今年 4 月 1 日 DateOnly aprilFirst = InDate.April;与现有日期逻辑衔接
DateOnly可以直接参与比较、作为数据库(如 EF Core 8+ 的date列)映射类型:
if (OnDate.January.The1st == DateOnly.FromDateTime(DateTime.Now)) { // 今天是今年元旦 }测试侧验证
Humanizer 官方测试在 tests/Humanizer.Tests/FluentDate/OnDateTests.cs 中对该 API 做了直接断言,例如:
[Fact] public void OnJanuaryThe23rd() => Assert.Equal(new(DateTime.Now.Year, 1, 23), OnDate.January.The23rd); [Fact] public void OnFebruaryThe() => Assert.Equal(new(DateTime.Now.Year, 2, 11), OnDate.February.The(11));更全面的覆盖见 GeneratedFluentDateTests.cs(该文件同样由生成逻辑产出,逐月逐日验证TheNth属性)。由于实现依赖DateTime.Now.Year,测试只断言"年份与当前一致",这也提示了一个使用注意点:测试中不要硬编码具体年份,除非先 mock 掉时钟。
使用注意事项与边界
- 年份语义:
OnDate所有成员都基于DateTime.Now.Year,无法直接表达"明年/去年某日"。跨年份的日期需要自行组合,例如new DateOnly(DateTime.Now.Year + 1, 12, 25),或借助InDate.TheYear(int)(见 InDate.cs)表达"某年 1 月 1 日"; - 框架限制:
OnDate仅对NET6_0_OR_GREATER目标框架编译,老框架请使用返回DateTime的 On.Days.cs; - 月份类型与属性数量:12 个月份类型中,属性生成数量按 2012 闰年计算,因此 2 月含
The29th,但平年调用它会在运行时构造失败;大月与小月属性个数不同(31 天 vs 30 天); - 可读性收益:
OnDate.January.The1st比new DateOnly(DateTime.Now.Year, 1, 1)更具自描述性,尤其适合在领域规则、报表默认值、配置默认日期等位置使用,使代码贴近业务语言。
小结
OnDate是 Humanizer FluentDate 家族中专为 .NET 6+DateOnly场景设计的流式日期构造器:通过 12 个嵌套月份类型 +The(int)方法 +The1st~The31st命名属性,把"今年某月某日"的构造过程变成了可读性极强的链式表达式。其源码由 OnDate.Days.tt 模板生成、由 OnDate.Days.cs 承载实现、并由 OnDateTests.cs 与 GeneratedFluentDateTests.cs 双重验证。当你需要把"特定日期"写进代码且希望它读起来像一句英文时,OnDate就是 Humanizer 给出的答案。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer InDate 完整指南:用 DateOnly 流式构造"某年某月"的日期
Humanizer InDate 完整指南:用 DateOnly 流式构造"某年某月"的日期 Humanizer 的 InDate 类提供了一套面向 Syste
开发工具Humanizer InDate.Four API 详解:用 DateOnly 构建"从某日起 4 天/周/月/年"的流式日期计算
Humanizer InDate.Four API 详解:用 DateOnly 构建"从某日起 4 天/周/月/年"的流式日期计算 本文基于 Humanizer
开发工具Humanizer OnDate 类详解:用流式语法构建 DateOnly 日期
Humanizer OnDate 类详解:用流式语法构建 DateOnly 日期 导读 Humanizer.OnDate 是 Humanizer 日期流式 AP
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考