☰
Humanizer InDate.Ten 详解:用 DateOnly 流式 API 计算 10 天/周/月/年后的日期
2026/9/25 2:42:40 网站建设 项目流程
  • 开发工具

【免费下载链接】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
点击查看免费下载

InDate.Ten是 Humanizer FluentDate(流式日期)API 中专门封装“以 10 为时间增量”的静态嵌套类,它围绕 .NET 6+ 的System.DateOnly类型提供了一组零配置、强类型、可读性极高的日期偏移工具:既支持以当前时刻(UTC)为基准取“从现在起 10 天后”等属性,也支持基于任意基准日期计算偏移的方法。读完本文,你将掌握InDate.Ten全部 4 个属性与 8 个方法的签名、语义与返回值,理解其底层实现与 T4 代码生成机制,并能在真实业务代码中把它与InDate的月份属性、InDate.Five等其他嵌套类组合使用。

一、InDate.Ten 是什么:FluentDate API 中的“十”号单元

Humanizer 的 FluentDate 目录提供了两类日期表达方式:一类是“绝对日期”,例如InDate.January(当年 1 月 1 日)、OnDate.April.The21st(4 月 21 日);另一类是“相对日期偏移”,即In、InDate下按 One 到 Ten 组织的嵌套静态类。InDate.Ten属于后者,它的定位非常单一:把“10 天 / 10 周 / 10 个月 / 10 年”这四种偏移量,以可读的静态属性和方法形式暴露出来。

public static class InDate.Ten

从源码结构看,InDate.Ten是public partial class InDate(InDate.cs)内部的一个public static class嵌套类型,整个类只包含静态成员,无需实例化,不依赖任何外部配置即可直接调用。它继承链上只有System.Object(即普通静态类),但它真正的价值不在于继承体系,而在于把最容易写错、最需要注释的“魔法数字日期偏移”变成了自文档化的 API 调用。

需要特别说明的适用前提:InDate这一 DateOnly 版本的 FluentDate API 整体以#if NET6_0_OR_GREATER编译指令包裹(见 InDate.SomeTimeFrom.cs),因为System.DateOnly是 .NET 6 才引入的类型;因此只有面向 .NET 6 及以上目标框架的项目才能使用InDate.Ten。如果你的项目运行在 .NET 5 或更早版本上,则应改用基于DateTime的In.Ten系列。

二、属性成员:基于当前 UTC 时刻的“从现在起”偏移

InDate.Ten提供 4 个只读静态属性,全部以DateTime.UtcNow为基准计算,返回System.DateOnly。它们的语义是“从现在起 10 个时间单位后是哪一天”:

属性说明签名返回值
Days10 days from now(从现在起 10 天后)public static System.DateOnly Days { get; }System.DateOnly
Weeks10 weeks from now(从现在起 10 周后)public static System.DateOnly Weeks { get; }System.DateOnly
Months10 months from now(从现在起 10 个月后)public static System.DateOnly Months { get; }System.DateOnly
Years10 years from now(从现在起 10 年后)public static System.DateOnly Years { get; }System.DateOnly

底层实现与“UTC 基准”语义

这四个属性并非黑盒,其实现直接写在 InDate.SomeTimeFrom.cs 的Ten类中,模式高度统一:

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));

值得注意的实现细节:

  • 基准统一为 UTC:属性基于DateTime.UtcNow而非DateTime.Now,从源码看这是有意设计——Days/Weeks/Months/Years全部以DateTime.UtcNow为起点,避免因服务器时区不同导致“今天”的判定漂移。测试 GeneratedFluentDateTests.cs 也专门以DateTime.UtcNow为基准做区间断言(Assert.InRange(actual, Add(before, amount, unit), Add(after, amount, unit))),验证属性返回值落在期望偏移区间内。
  • 周的单位换算:Weeks不是调用AddWeeks(DateOnly/DateTime也没有该方法),而是直接换算成天数——10 * 7 = 70天。这与Days的实现同源。
  • 月/年的日历语义:AddMonths(10)与AddYears(10)是日历运算,会自动处理大小月与闰年。例如在 1 月 31 日调用MonthsFrom,得到的是 11 月的月末日期,由 .NET 的日历算法决定(如 1 月 31 日 + 10 个月 = 11 月 30 日)。
  • 时间分量被丢弃:偏移计算完成后通过DateOnly.FromDateTime(...)只保留日期部分,时分秒及偏移时刻(UTC)均不保留,所以属性返回的是纯粹“哪一天”的概念。

典型用法示例

using Humanizer; // 从现在起 10 天后的日期(只含日期,无时间分量) DateOnly inTenDays = InDate.Ten.Days; // 从现在起 10 个月后的日期,例如用于订阅续期提醒 DateOnly renewalDate = InDate.Ten.Months; // 直接从控制台输出 Console.WriteLine(InDate.Ten.Years); // 例如 2036-09-24(依调用时刻而定)

注意属性是“即时计算”的:每次访问都会重新读取DateTime.UtcNow并重新计算,因此同一次运行中两次读取属性,若恰逢午夜切换,返回值可能差一天;若你需要在同一基准下取多个偏移,建议先固定一个基准日期,再使用方法成员(见下一节)。

三、方法成员:基于指定基准日期的偏移计算

InDate.Ten的 8 个方法分为两组:对System.DateOnly基准的 4 个重载、对System.DateTime基准的 4 个重载。它们的语义统一为“距离给定日期 10 个时间单位后的日期”,且返回值一律是System.DateOnly,即使入参是DateTime也是如此。

方法说明签名
DaysFrom(DateOnly)10 days from the provided datepublic static System.DateOnly DaysFrom(System.DateOnly date)
DaysFrom(DateTime)10 days from the provided datepublic static System.DateOnly DaysFrom(System.DateTime date)
WeeksFrom(DateOnly)10 weeks from the provided datepublic static System.DateOnly WeeksFrom(System.DateOnly date)
WeeksFrom(DateTime)10 weeks from the provided datepublic static System.DateOnly WeeksFrom(System.DateTime date)
MonthsFrom(DateOnly)10 months from the provided datepublic static System.DateOnly MonthsFrom(System.DateOnly date)
MonthsFrom(DateTime)10 months from the provided datepublic static System.DateOnly MonthsFrom(System.DateTime date)
YearsFrom(DateOnly)10 years from the provided datepublic static System.DateOnly YearsFrom(System.DateOnly date)
YearsFrom(DateTime)10 years from the provided datepublic static System.DateOnly YearsFrom(System.DateTime date)

两种重载的源码实现差异

以DaysFrom为例(InDate.SomeTimeFrom.cs):

public static DateOnly DaysFrom(DateOnly date) => date.AddDays(10); public static DateOnly DaysFrom(DateTime date) => DateOnly.FromDateTime(date.AddDays(10));
  • DateOnly 重载:直接在DateOnly上调用AddDays(10),实现最轻量,没有任何类型转换。
  • DateTime 重载:先在DateTime上做AddDays(10)偏移(此时保留原时间分量),再经DateOnly.FromDateTime(...)截断为纯日期。也就是说,当你传入带时间的DateTime时,返回结果不会保留传入的时分秒,只保留日期。

WeeksFrom同样换算为天数(AddDays(70)),MonthsFrom使用AddMonths(10),YearsFrom使用AddYears(10),规律与属性一致。

参数注意事项

  • 参数date接收System.DateOnly或System.DateTime,两种重载可无缝混用——例如从数据库读出的DateTime和业务层新建的DateOnly都能直接传入。
  • 日历运算的边界行为由 .NET 运行时决定:月末日期 +10 个月时,返回值会由AddMonths的钳制规则调整到目标月份的有效日期;闰年 2 月 29 日 +10 年时,AddYears会按 .NET 的规则钳制(如返回 3 月 1 日或 2 月 28 日,取决于具体目标年份)。测试 GeneratedFluentDateTests.cs 甚至专门用闰日2024-02-29作为基准来断言From系列方法的偏移结果,说明这类边界是 API 语义的一部分。

典型用法示例

using Humanizer; // 以固定基准计算,保证多次取值的基准一致 var baseDate = new DateOnly(2026, 9, 24); DateOnly d = InDate.Ten.DaysFrom(baseDate); // 2026-10-04 DateOnly w = InDate.Ten.WeeksFrom(baseDate); // 2026-12-03 DateOnly m = InDate.Ten.MonthsFrom(baseDate); // 2027-07-24 DateOnly y = InDate.Ten.YearsFrom(baseDate); // 2036-09-24 // DateTime 入参同样可用,返回值仍是 DateOnly var baseDateTime = new DateTime(2026, 9, 24, 15, 30, 0, DateTimeKind.Utc); DateOnly result = InDate.Ten.DaysFrom(baseDateTime); // 2026-10-04(时间分量被丢弃)

四、从 T4 模板到成品代码:InDate.Ten 的生成机制

InDate.Ten并不是手工逐个编写的,而是由 T4 文本模板驱动生成。模板 InDate.SomeTimeFrom.tt 中的核心循环如下:

<#for (var i = 1; i <= 10; i++){ var plural = i > 1 ? "s" : ""; var day = "Day" + plural; var week = "Week" + plural; var month = "Month" + plural; var year = "Year" + plural; #> public static class <#= i.ToWords().Dehumanize() #> { public static DateOnly <#= day #> => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(<#= i #>)); // ……Weeks/Months/Years 与各类 From 方法同理 } <#}#>

这段模板揭示了三个关键事实:

  1. One 到 Ten 的十个嵌套类是同一模板的产物:循环变量i从 1 遍历到 10,每个迭代生成一个以英文单词命名的静态类(One、Two……Ten)。InDate.Ten对应i = 10的迭代。
  2. 类名来自 Humanizer 自身的能力:i.ToWords().Dehumanize()把整数转成英文单词(如10 → "Ten"),这恰好演示了 Humanizer 的“吃自己的狗粮”——用库内的数字转单词与反人性化 API 生成自己的代码。
  3. 单复数命名规则:plural = i > 1 ? "s" : ""决定了成员命名——i = 10时生成复数形式的Days、Weeks、Months、Years和DaysFrom、WeeksFrom、MonthsFrom、YearsFrom(这与InDate.One的Day、Month等单数命名形成对照,见 InDate.SomeTimeFrom.cs)。

因此,本文档中看到的InDate.Ten全部成员,其命名、签名和实现模式都严格受控于模板;后续若要新增更大的数值(如 Eleven、Twenty),改模板并重新生成即可,这保证了 FluentDate API 全家族的结构一致性。

五、测试佐证:GeneratedFluentDateTests 如何验证 Ten

仓库用反射式测试对生成的 FluentDate API 做全覆盖验证,InDate.Ten的成员同样被纳入其中(GeneratedFluentDateTests.cs):

  • 属性测试(InDateRelativeDatePropertiesReturnExpectedUtcOffsets,L34-L35):遍历InDate下的所有相对日期嵌套类型,对每个静态属性在调用前后分别取DateTime.UtcNow,断言返回值落在[Add(before), Add(after)]区间内,从而验证InDate.Ten.Days等属性确实是“当前 UTC 时刻 + 对应偏移”。
  • DateOnly/DateTime 方法测试(InDateRelativeDateOnlyMethodsReturnExpectedOffsetsFromProvidedDate与InDateRelativeDateTimeMethodsReturnExpectedOffsetsFromProvidedDateTime,L38-L43):以固定基准(含 2024-02-29 闰日)调用From方法,并用Assert.Equal精确断言偏移结果,覆盖InDate.Ten.DaysFrom、WeeksFrom、MonthsFrom、YearsFrom的全部 8 个重载。
  • 数量映射(L329-L341):RelativeAmounts字典把"Ten"映射为10,测试据此计算期望偏移量,与模板中的i = 10一致。

另外,手写的 InDateTests.cs 展示了该类家族的直接使用方式(如InDate.Five.DaysFrom(baseDate)),InDate.Ten的调用形态与之完全相同,可互为参考。

六、实战组合:把 InDate.Ten 融入日期业务逻辑

InDate.Ten单独使用价值有限,它的设计意图是与InDate家族的其他成员组合,写出可读性极强的日期逻辑:

using Humanizer; // 场景 1:订阅到期提醒 —— 固定基准 + 多种偏移组合 var signupDate = new DateOnly(2026, 9, 24); var trialEnd = InDate.Ten.DaysFrom(signupDate); // 试用期:10 天后 var planReview = InDate.Ten.MonthsFrom(signupDate); // 复查节点:10 个月后 // 场景 2:与绝对日期 API 组合 —— 从“今年 1 月 1 日”起算 var yearStart = InDate.January; // 当年 1 月 1 日 var reportDue = InDate.Ten.MonthsFrom(yearStart); // 当年 11 月 1 日 // 场景 3:与 OnDate 组合 —— 从“4 月 21 日”起算 10 周 var anchor = OnDate.April.The21st; // 当年 4 月 21 日 var milestone = InDate.Ten.WeeksFrom(anchor); // 其后第 10 周 // 场景 4:与 InDate.Five 等其他增量类对照 var fiveYearsLater = InDate.Five.Years; // 从现在起 5 年后 var tenYearsLater = InDate.Ten.Years; // 从现在起 10 年后

使用时的几条建议:

  • 固定基准优先:需要同一基准下的多个偏移时,先取基准(如var base = InDate.January;),再连续调用From方法,避免多次访问“now 属性”导致基准漂移。
  • 注意返回值类型:所有属性与方法都返回DateOnly,如果你的下游 API 需要DateTime,请自行转换(如InDate.Ten.Days.ToDateTime(TimeOnly.MinValue))。
  • 框架版本限制:InDate.Ten仅在NET6_0_OR_GREATER编译条件下可用;.NET 5 及以下请使用基于DateTime的 In.Ten。

七、快速参考

InDate.Ten成员一览(public static,命名空间Humanizer):

  • 属性:Days、Weeks、Months、Years(基于DateTime.UtcNow,返回DateOnly)
  • 方法:DaysFrom、WeeksFrom、MonthsFrom、YearsFrom,每种均有DateOnly与DateTime两个重载,统一返回DateOnly

关联源码与测试:

  • 实现:src/Humanizer/FluentDate/InDate.SomeTimeFrom.cs
  • 生成模板:src/Humanizer/FluentDate/InDate.SomeTimeFrom.tt
  • 容器类:src/Humanizer/FluentDate/InDate.cs
  • 反射测试:tests/Humanizer.Tests/FluentDate/GeneratedFluentDateTests.cs
  • 直接使用示例:tests/Humanizer.Tests/FluentDate/InDateTests.cs
  • 开发工具

【免费下载链接】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
点击查看免费下载
上一篇:3大痛点突破!JeecgBoot模型配置测试效率优化指南
下一篇:1Panel项目中Nginx反向代理配置解析的优化实践

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

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

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

立即咨询