☰
Humanizer 流式日期 API 深度解析:`InDate.Ten` 与“10 天/周/月/年后“的声明式日期计算
2026/9/27 11:09:54 网站建设 项目流程
  • 开发工具

【免费下载链接】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 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),时间会被丢弃

两点值得注意:

  1. 返回值统一是DateOnly。即使传入DateTime,得到的结果也只有年月日,没有时分秒。这与In.Ten(针对DateTime的版本)不同,后者保留完整时刻。
  2. 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. 数量范围固定为 1~10:模板循环i从 1 到 10,因此只生成One、Two……Ten十个嵌套类,超过 10 的数量没有对应 API。
  2. 类名由 Humanizer 自身的能力生成:i.ToWords()(数字转英文单词)与.Dehumanize()(单词反人化)正是 Humanizer 的数字与字符串扩展,模板用它们把1变成类名One、把10变成Ten。
  3. 单复数按数量自动切换: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

项目地址:https://gitcode.com/gh_mirrors/hu/Humanizer
点击查看免费下载
上一篇:如何快速跑通 whisper-tiny.en 英语语音识别模型
下一篇:上手 HiGHS:5 分钟跑通你的第一个线性规划

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

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

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

立即咨询