- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
本指南以 Humanizer 2.13.14 版本 API 参考文档 Humanizer.InDate.Ten.md 为核心,系统讲解InDate.Ten这一静态类型:它把"10 天/10 周/10 个月/10 年后"这类时间偏移,封装成一组可读性极强的属性与方法,并同时支持DateOnly与DateTime两种基准类型。读完本文,你将掌握InDate家族(InDate.One至InDate.Ten)的完整用法、底层生成机制与日历运算语义,能够在业务代码中写出"今天起 10 天后"这样的自解释表达式。
一、InDate.Ten是什么
InDate.Ten是 Humanizer 流式日期(FluentDate)API 家族中的一个静态嵌套类,完整定义位于 InDate.SomeTimeFrom.cs:
public static class InDate.Ten它继承自System.Object,本身不持有任何实例状态,全部成员均为static。从命名即可看出其设计意图:用英文数字词(Ten)作为类名,用复数单位(Days、Weeks、Months、Years)作为成员名,让调用代码读起来像一句自然语言。
整个类由两大成员族构成:
| 成员族 | 成员 | 语义 |
|---|---|---|
| 属性(4 个) | Days、Weeks、Months、Years | 相对当前时刻(UTC)偏移 10 个单位 |
| 方法(8 个) | DaysFrom、WeeksFrom、MonthsFrom、YearsFrom,各有DateOnly与DateTime两个重载 | 相对调用者提供的基准日期偏移 10 个单位 |
所有属性与方法的返回值类型都是System.DateOnly,这也是整个InDate家族与面向DateTime的In家族(见 Humanizer.In.Ten.md)最本质的区别。
二、"从现在起"属性族:Days/Weeks/Months/Years
四个无参属性全部返回DateOnly,语义为"从当前时刻起 10 个 X 单位之后的那一天":
public static System.DateOnly Days { get; } public static System.DateOnly Weeks { get; } public static System.DateOnly Months { get; } public static System.DateOnly Years { get; }对应的源码实现(InDate.SomeTimeFrom.cs)揭示了三个关键细节:
public static DateOnly Days => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(10)); public static DateOnly Weeks => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(70)); public static DateOnly Months => DateOnly.FromDateTime(DateTime.UtcNow.AddMonths(10)); public static DateOnly Years => DateOnly.FromDateTime(DateTime.UtcNow.AddYears(10));要点一:基准是UtcNow而非Now。四个属性统一以DateTime.UtcNow为计算起点,再经DateOnly.FromDateTime截去时间部分、只保留日期。也就是说,无论代码运行在哪个时区,"X 天后"的日期始终按 UTC 时刻推算,DateOnly返回值本身不带时区信息。
要点二:周被折算为天。Weeks的底层是AddDays(70)(10 × 7),而不是AddWeeks——Bcl 的DateTime/DateOnly本身没有AddWeeks方法,Humanizer 直接用天来折算,语义等价于"70 天后"。
要点三:月与年是日历运算。Months/Years委托给AddMonths(10)/AddYears(10),属于"日历粒度"的推移(详见本文第六节的语义辨析),而不是按固定 30 天/365 天估算的时长偏移。
三、指定基准日的方法族:8 个XxxFrom重载
当"10 天后"需要从某个特定日期(而非当前时刻)开始计算时,使用From方法。InDate.Ten为四种单位各提供一对重载,共 8 个方法:
public static System.DateOnly DaysFrom(System.DateOnly date); public static System.DateOnly DaysFrom(System.DateTime date); public static System.DateOnly WeeksFrom(System.DateOnly date); public static System.DateOnly WeeksFrom(System.DateTime date); public static System.DateOnly MonthsFrom(System.DateOnly date); public static System.DateOnly MonthsFrom(System.DateTime date); public static System.DateOnly YearsFrom(System.DateOnly date); public static System.DateOnly YearsFrom(System.DateTime date);两组重载的差异与实现策略(源码见 InDate.SomeTimeFrom.cs):
| 重载 | 实现 | 特点 |
|---|---|---|
XxxFrom(DateOnly date) | date.AddDays(10)/date.AddMonths(10)/date.AddYears(10) | 直接在DateOnly上做日历运算,无时区、无时刻概念,结果确定性强 |
XxxFrom(DateTime date) | DateOnly.FromDateTime(date.AddDays(10))等 | 先对DateTime做偏移,再截取日期部分返回DateOnly;若传入带时间分量(如2025-01-20 09:00),时间会被丢弃 |
两点值得注意:
- 返回值统一是
DateOnly。即使传入DateTime,得到的结果也只有年月日,没有时分秒。这与In.Ten(针对DateTime的版本)不同,后者保留完整时刻。 WeeksFrom与DaysFrom计算结果一致:WeeksFrom(date)等价于DaysFrom(date)叠加 7 次,因为两者底层都落到AddDays(70 与 10)。
四、使用示例:从"写代码"到"读代码"
以官方场景示例 fluent-dates-and-time-spans.mdx 为背景,InDate.Ten的典型用法如下:
using Humanizer; // 1. 相对当前时刻:今天起 10 天 / 10 周 / 10 个月 / 10 年后 DateOnly tenDays = InDate.Ten.Days; // DateTime.UtcNow 之后第 10 天 DateOnly tenWeeks = InDate.Ten.Weeks; // UtcNow 之后第 70 天 DateOnly tenMonths = InDate.Ten.Months; // UtcNow 之后第 10 个月(日历推移) DateOnly tenYears = InDate.Ten.Years; // UtcNow 之后第 10 年 // 2. 相对指定基准:从某个固定日期起算(推荐用于可重复执行的代码) var startingPoint = new DateTime(2025, 1, 20, 9, 0, 0); DateOnly reviewDate = InDate.Ten.MonthsFrom(startingPoint); // 2025-11-20 // 3. DateOnly 基准同样适用 DateOnly baseDay = new(2025, 2, 20); DateOnly dueDate = InDate.Ten.DaysFrom(baseDay); // 2025-03-02 DateOnly anniversary = InDate.Ten.YearsFrom(baseDay); // 2035-02-20测试用例 InDateTests.cs 印证了同样的用法模式:
[Fact] public void InFiveDays() { var baseDate = OnDate.January.The21st; var date = InDate.Five.DaysFrom(baseDate); Assert.Equal(baseDate.AddDays(5), date); }这段测试同时展示了InDate与OnDate的组合用法:先用OnDate.January.The21st构造基准日,再交给InDate.Five.DaysFrom完成偏移计算。
五、源码溯源:InDate.One至InDate.Ten由 T4 模板批量生成
InDate.Ten并非手写代码,而是由 T4 文本模板 InDate.SomeTimeFrom.tt 生成的。模板的核心循环如下:
<#for (var i = 1; i <= 10; i++){ var plural = i > 1 ? "s" : ""; var day = "Day" + plural; ... #> public static class <#= i.ToWords().Dehumanize() #> { public static DateOnly <#= day #> => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(<#= i #>)); ... } <#}#>这个模板揭示了整个家族的三条生成规律:
- 数量范围固定为 1~10:模板循环
i从 1 到 10,因此只生成One、Two……Ten十个嵌套类,超过 10 的数量没有对应 API。 - 类名由 Humanizer 自身的能力生成:
i.ToWords()(数字转英文单词)与.Dehumanize()(单词反人化)正是 Humanizer 的数字与字符串扩展,模板用它们把1变成类名One、把10变成Ten。 - 单复数按数量自动切换:
plural = i > 1 ? "s" : ""使得One类里是单数成员Day/Week/Month/Year,而Ten类里全部是复数成员Days/Weeks/Months/Years——这也是为什么InDate.Ten.Days是复数而InDate.One.Day是单数。
整个InDate类型是public partial class(InDate.cs 定义了TheYear与月份属性/XxxOf(year)方法,InDate.SomeTimeFrom.cs 定义了数量类),各文件通过partial合并为一个完整类型。全部代码被#if NET6_0_OR_GREATER条件编译包裹——因为System.DateOnly自 .NET 6 才引入,InDate家族(包括InDate.Ten)仅在 .NET 6 及以上目标框架中可用,这是使用前必须确认的前提。
六、语义边界与最佳实践
日历运算 ≠ 时长运算
MonthsFrom/YearsFrom委托AddMonths/AddYears,是日历粒度的推移,会遵循目标月份的"归一化"规则:例如 2 月 29 日加 1 年,在非闰年会归一化为 2 月 28 日。而Days/Weeks属于固定时长,一天始终是 24 小时。官方场景指南 fluent-dates-and-time-spans.mdx 明确建议:固定时长用1.5.Days()这类NumberToTimeSpanExtensions扩展(如 NumberToTimeSpanExtensions.cs),日历推算才用In/InDate家族,两者不要混用。
无参属性不适合确定性代码
InDate.Ten.Days依赖DateTime.UtcNow,每次调用结果随当前时刻漂移,导致测试不可复现。官方指南的明确建议是:需要可重复执行的代码(测试、批处理、定时任务断言)应注入基准日期,改用XxxFrom(date)。同理,InDate的月份属性(如InDate.April)读取的是当前年份,测试中应优先使用InDate.AprilOf(2025)这类显式年份形式。
DateOnly 与 DateTime 的选择
- 只需要"某一天"(不含时刻)时,优先
DateOnly重载:无时区歧义、确定性最强; - 需要保留时刻参与运算时,用
DateTime重载传入,但要注意返回值会被截断为DateOnly; - 需要同时保留"偏移后的完整时刻",应改用
In家族(如In.Ten.DaysFrom(startingPoint),返回DateTime)。
七、相关 API 导航
InDate.Ten不是孤立类型,它与以下 API 构成完整的流式日期体系,可对照查阅:
- Humanizer.InDate.md:父类型
InDate,提供 12 个月份属性、XxxOf(int year)方法与TheYear(int year) - Humanizer.InDate.One.md 至
InDate.Nine:Ten的九个"兄弟类",成员采用单数/复数命名,结构完全同构 - Humanizer.In.Ten.md:面向
DateTime的对应版本,返回DateTime而非DateOnly - Humanizer.OnDate.md:构造指定日期(如
OnDate.January.The21st),常与InDate.XxxFrom组合使用 - Humanizer.InDate.Months.cs 与 InDate.Months.tt:月份/年份相关成员的生成模板与产物
- 场景指南 fluent-dates-and-time-spans.mdx:流式日期与时长 API 的完整使用建议
小结
InDate.Ten是 Humanizer 流式日期设计中"数量 × 单位"二维网格上的一个单元:4 个from now属性覆盖当前时刻基准,8 个XxxFrom方法覆盖显式基准,统一返回DateOnly,底层由 T4 模板从同一套逻辑生成以保证 1~10 的命名与语义完全一致。掌握它的关键是记住三点:基准时刻用UtcNow、周折算为天、月/年为日历运算;在实际项目中,优先用From方法注入基准日期,以换取确定性与可测试性。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 流式日期 API 详解:InDate.Ten 的 10 天 / 周 / 月 / 年日期计算
Humanizer 流式日期 API 详解:InDate.Ten 的 10 天 / 周 / 月 / 年日期计算 本篇技术指南聚焦 Humanizer 流式日期(
开发工具Humanizer InDate.Ten 详解:用 DateOnly 流式 API 计算 10 天/周/月/年后的日期
Humanizer InDate.Ten 详解:用 DateOnly 流式 API 计算 10 天/周/月/年后的日期 InDate.Ten 是 Humaniz
开发工具Humanizer 流式日期 API 详解:InDate.Ten 实现 10 天/周/月/年的 DateOnly 日期计算
Humanizer 流式日期 API 详解:InDate.Ten 实现 10 天/周/月/年的 DateOnly 日期计算 Humanizer 的 InDate
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考