- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
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 个时间单位后是哪一天”:
| 属性 | 说明 | 签名 | 返回值 |
|---|---|---|---|
Days | 10 days from now(从现在起 10 天后) | public static System.DateOnly Days { get; } | System.DateOnly |
Weeks | 10 weeks from now(从现在起 10 周后) | public static System.DateOnly Weeks { get; } | System.DateOnly |
Months | 10 months from now(从现在起 10 个月后) | public static System.DateOnly Months { get; } | System.DateOnly |
Years | 10 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 date | public static System.DateOnly DaysFrom(System.DateOnly date) |
DaysFrom(DateTime) | 10 days from the provided date | public static System.DateOnly DaysFrom(System.DateTime date) |
WeeksFrom(DateOnly) | 10 weeks from the provided date | public static System.DateOnly WeeksFrom(System.DateOnly date) |
WeeksFrom(DateTime) | 10 weeks from the provided date | public static System.DateOnly WeeksFrom(System.DateTime date) |
MonthsFrom(DateOnly) | 10 months from the provided date | public static System.DateOnly MonthsFrom(System.DateOnly date) |
MonthsFrom(DateTime) | 10 months from the provided date | public static System.DateOnly MonthsFrom(System.DateTime date) |
YearsFrom(DateOnly) | 10 years from the provided date | public static System.DateOnly YearsFrom(System.DateOnly date) |
YearsFrom(DateTime) | 10 years from the provided date | public 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 方法同理 } <#}#>这段模板揭示了三个关键事实:
- One 到 Ten 的十个嵌套类是同一模板的产物:循环变量
i从 1 遍历到 10,每个迭代生成一个以英文单词命名的静态类(One、Two……Ten)。InDate.Ten对应i = 10的迭代。 - 类名来自 Humanizer 自身的能力:
i.ToWords().Dehumanize()把整数转成英文单词(如10 → "Ten"),这恰好演示了 Humanizer 的“吃自己的狗粮”——用库内的数字转单词与反人性化 API 生成自己的代码。 - 单复数命名规则:
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
相关推荐
Humanizer 流式日期 API 详解:InDate.Ten 的 10 天 / 周 / 月 / 年日期计算
Humanizer 流式日期 API 详解:InDate.Ten 的 10 天 / 周 / 月 / 年日期计算 本篇技术指南聚焦 Humanizer 流式日期(
开发工具Humanizer InDate.Four 详解:用流式 API 生成"4 天/周/月/年后"的 DateOnly 日期
Humanizer InDate.Four 详解:用流式 API 生成"4 天/周/月/年后"的 DateOnly 日期 Humanizer 的 FluentD
开发工具Humanizer InDate.Six 详解:用 DateOnly 流畅表达「六天后 / 六周后 / 六月后 / 六年后」的日期计算
Humanizer InDate.Six 详解:用 DateOnly 流畅表达「六天后 / 六周后 / 六月后 / 六年后」的日期计算 导读 InDate.Si
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考