news 2026/9/29 5:58:14

Humanizer InDate.Four 详解:用 DateOnly 流式表达“4 天后 / 4 周后 / 4 月后 / 4 年后”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Humanizer InDate.Four 详解:用 DateOnly 流式表达“4 天后 / 4 周后 / 4 月后 / 4 年后”
  • 开发工具

【免费下载链接】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.Four是 Humanizer FluentDate 流式日期 API 中的一个静态嵌套类,专门面向 .NET 6+ 引入的System.DateOnly类型,提供“从现在起 4 天/4 周/4 个月/4 年”以及“从任意给定日期起 4 天/4 周/4 个月/4 年”的计算能力。阅读本文后,你将掌握InDate.Four全部 4 个属性与 8 个方法重载的签名与语义、其背后由 T4 模板生成的源码实现、基于UtcNow与AddMonths/AddYears的日历运算原理,以及如何在可重复执行的测试与业务代码中正确使用这类流式日期成员。

InDate.Four 是什么:API 全景

在 Humanizer 3.0.10 的 API 文档中,InDate.Four被定义为public static class InDate.Four,继承自System.Object,是InDate这个静态 partial 类内部按数字分组的嵌套静态类之一。它的职责非常单一:把所有与“4”相关的相对日期计算集中在一个命名空间中,通过英文数字单词Four直接暴露给调用者。

InDate.Four由两类成员组成:

  • 属性(4 个):Days、Weeks、Months、Years,语义是“从当前时刻算起的相对日期”,均返回System.DateOnly;
  • 方法(8 个):DaysFrom、WeeksFrom、MonthsFrom、YearsFrom,每个都提供DateOnly与DateTime两个输入重载,语义是“从指定日期算起的相对日期”,返回值统一为System.DateOnly。

完整成员签名如下(与 Humanizer.InDate.Four.md 文档一致):

成员签名语义
属性public static System.DateOnly Days { get; }从现在起 4 天
属性public static System.DateOnly Weeks { get; }从现在起 4 周
属性public static System.DateOnly Months { get; }从现在起 4 个月
属性public static System.DateOnly Years { get; }从现在起 4 年
方法public static System.DateOnly DaysFrom(System.DateOnly date)从给定日期起 4 天
方法public static System.DateOnly DaysFrom(System.DateTime date)从给定日期起 4 天
方法public static System.DateOnly WeeksFrom(System.DateOnly date)从给定日期起 4 周
方法public static System.DateOnly WeeksFrom(System.DateTime date)从给定日期起 4 周
方法public static System.DateOnly MonthsFrom(System.DateOnly date)从给定日期起 4 个月
方法public static System.DateOnly MonthsFrom(System.DateTime date)从给定日期起 4 个月
方法public static System.DateOnly YearsFrom(System.DateOnly date)从给定日期起 4 年
方法public static System.DateOnly YearsFrom(System.DateTime date)从给定日期起 4 年

属性成员:基于 UtcNow 的“从当下算起”

四个属性在 InDate.SomeTimeFrom.cs 中的Four类里均有直接对应的实现,全部以DateTime.UtcNow为时间基准:

public static class Four { public static DateOnly Days => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(4)); public static DateOnly Weeks => DateOnly.FromDateTime(DateTime.UtcNow.AddDays(28)); public static DateOnly Months => DateOnly.FromDateTime(DateTime.UtcNow.AddMonths(4)); public static DateOnly Years => DateOnly.FromDateTime(DateTime.UtcNow.AddYears(4)); }

几个值得注意的实现细节:

  • 周按 7 天折算:Weeks不是调用AddWeeks(DateOnly本身也不提供该方法),而是直接AddDays(28),即 4 × 7 天,这与人类直觉中的“一周七天”完全一致。
  • UTC 时间基准:属性读取的是DateTime.UtcNow而不是DateTime.Now,随后通过DateOnly.FromDateTime截断为纯日期(不含时间分量)。这意味着结果不依赖部署机器的本地时区,行为是可预测的。
  • 动态求值:属性是表达式体(expression-bodied)的只读属性,每次访问都会重新取UtcNow计算,而不是在类加载时缓存。因此同一次程序运行中先后两次读取InDate.Four.Days,在跨 UTC 午夜边界时可能得到不同的日期。

官方场景指南 fluent-dates-and-time-spans.mdx 也明确指出:不带From或Of(year)的属性会读取DateTime.Now或UtcNow,应避免在确定性代码(如测试断言)中直接使用——这正是下面要重点介绍*From方法的原因。

方法成员:从任意基准日期计算

四个*From方法提供双重重载,分别接收DateOnly与DateTime,并把任意输入统一归一化为DateOnly输出:

// DateOnly 输入:直接复用 AddDays/AddMonths/AddYears public static DateOnly DaysFrom(DateOnly date) => date.AddDays(4); public static DateOnly WeeksFrom(DateOnly date) => date.AddDays(28); public static DateOnly MonthsFrom(DateOnly date) => date.AddMonths(4); public static DateOnly YearsFrom(DateOnly date) => date.AddYears(4); // DateTime 输入:先做日历运算,再转 DateOnly public static DateOnly DaysFrom(DateTime date) => DateOnly.FromDateTime(date.AddDays(4)); public static DateOnly WeeksFrom(DateTime date) => DateOnly.FromDateTime(date.AddDays(28)); public static DateOnly MonthsFrom(DateTime date) => DateOnly.FromDateTime(date.AddMonths(4)); public static DateOnly YearsFrom(DateTime date) => DateOnly.FromDateTime(date.AddYears(4));

两个重载的差异在于运算发生的层级:

  • 传入DateOnly时,直接在DateOnly上进行AddDays/AddMonths/AddYears,结果天然是DateOnly;
  • 传入DateTime时,先调用DateTime的AddDays/AddMonths/AddYears(保留时间分量做进位处理),再通过DateOnly.FromDateTime丢弃时间、取出日期部分。

这种设计让调用方无论持有哪种日期类型,都能以一致的DateOnly结果收尾,非常适合需要“只关心日期、不关心时间”的业务场景,例如预约、排期、账单周期计算。

日历运算的归一化语义

MonthsFrom与YearsFrom直接委托给AddMonths/AddYears,因此继承 .NET 标准的月末归一化(normalization)规则:当月不存在同一天时,结果自动折叠到该月的最后一天。例如从 2 月 29 日(闰年)加上 4 个月,落在 6 月 29 日;而如果从某年 1 月 31 日加 1 个月,会得到 2 月 28 日(平年)而非抛异常。官方场景文档 fluent-dates-and-time-spans.mdx 对这一点有明确说明,并强调这类运算是日历粒度的操作,与把TimeSpan直接累加到日期上的“固定时长估算”有本质区别。

源码视角:partial class 与 T4 模板生成

InDate.Four只是整个 FluentDate 家族的一个切片。从源码结构看,InDate是一个跨越多个文件的public partial class:

  • InDate.cs:提供TheYear(int year),返回指定年份的 1 月 1 日;
  • InDate.Months.cs:提供January、JanuaryOf(int year)等十二个月份成员,由 InDate.Months.tt 生成;
  • InDate.SomeTimeFrom.cs:包含One到Ten十个嵌套静态类,Four是其中之一。

One~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. 命名来自单词转换:类名用i.ToWords().Dehumanize()生成——即先经 Humanizer 的数字转单词能力得到"four",再经 Dehumanize 规范化为 PascalCase 的Four。这正是One~Ten类名统一风格的来源,也展示了 Humanizer 自身的扩展方法在项目内部的复用。
  2. 成员命名遵循单复数:i == 1时属性名是单数Day/Week/Month/Year,i > 1时带复数后缀Days/Weeks/Months/Years(见InDate.SomeTimeFrom.cs中One类与Two~Ten类的命名差异)。
  3. 生成的 .cs 与 .tt 一一对应:Humanizer.csproj 中配置了TextTemplatingFileGenerator,将InDate.SomeTimeFrom.tt与生成的InDate.SomeTimeFrom.cs关联,确保仓库中提交的代码与模板保持同步。

版本与条件编译:为什么只有 .NET 6+ 可用

DateOnly是 .NET 6 新增的类型,因此 Humanizer 对整套InDate数字级成员都加了条件编译保护。在 InDate.SomeTimeFrom.cs 与 InDate.Months.cs 的顶部都可以看到:

#if NET6_0_OR_GREATER namespace Humanizer; ... #endif

也就是说,只有目标框架满足NET6_0_OR_GREATER时(例如net8.0、net10.0、net11.0),InDate.Four才会被编译进程序集;而在net48、netstandard2.0等旧框架目标下,该成员不存在。这一点与 Humanizer.csproj 中声明的多目标框架(net11.0;net10.0;net8.0;net48;netstandard2.0)是一致的。如果你的项目仍基于旧框架,应改用面向DateTime的 In.SomeTimeFrom.cs(In.Four.Days、In.Four.MonthsFrom(...)等),其成员返回System.DateTime,且额外提供了Second、Minute、Hour等更细粒度的单位。

实战:在可重复测试与业务代码中使用

确定性优先:总是注入基准日期

由于不带From的属性依赖UtcNow,在单元测试中直接断言InDate.Four.Days会导致测试结果随时间漂移。正确的做法是用*From方法传入固定基准。仓库自带的测试 InDateTests.cs 展示了这一模式(针对Five,但Four同理):

[Fact] public void InFiveDays() { var baseDate = OnDate.January.The21st; var date = InDate.Five.DaysFrom(baseDate); Assert.Equal(baseDate.AddDays(5), date); }

测试先构造固定的DateOnly基准,再用被测 API 计算结果并断言,保证结果与运行时刻无关。

与 In.Two.MonthsFrom 组合的完整示例

官方示例 scenarios-fluent-dates/Program.cs 演示了同族 API(In系列,返回DateTime)的用法:以固定起点2025-01-20 09:00调用In.Two.MonthsFrom(startingPoint),得到2025-03-20 09:00(输出见 expected-output.txt)。把In换成InDate,即可获得同样语义的DateOnly版本:

using Humanizer; // 业务场景:计算 4 个月后的日期(纯日期,无时间分量) var renewalDate = InDate.Four.MonthsFrom(DateOnly.FromDateTime(DateTime.Now)); // 混合输入:DateTime 输入也会被归一化为 DateOnly var dueDate = InDate.Four.WeeksFrom(new DateTime(2026, 9, 28, 9, 30, 0)); // dueDate 的类型是 DateOnly,值为 2026-10-26

语义选择建议

  • 需要固定时间长度(如“恰好 4 个 7 天”)时,Weeks/Days是确定性的:AddDays(28)永远是 28 天;
  • 需要日历语义(如“下个月的同一天”)时,Months/Years会按AddMonths/AddYears的月末归一化规则折叠,极端日期需注意结果可能不是“同一天”;
  • 需要确定可复现时,避免无参属性,优先*From(date)注入基准;需要纯日期、无时区干扰时,优先DateOnly版本的InDate,而非DateTime版本的In。

延伸阅读

  • InDate 类 API 参考:十二个月份、TheYear与One~Ten全量成员
  • In.Four 类 API 参考:面向DateTime的同名数字级成员
  • InDate 源码 与 T4 生成模板
  • 流式日期与时间跨度场景指南 与 可运行示例
  • InDate 单元测试:确定性基准的测试写法
  • 开发工具

【免费下载链接】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
点击查看免费下载
上一篇:VisualCppRedist AIO:Windows系统依赖修复的终极魔法棒
下一篇:如何快速将B站缓存m4s视频转换为通用MP4格式:新手完整指南

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 5:57:05

VScode插件推荐以及settings配置:用TaoToken统一Key接入AI编程插件

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 5:54:08

H5振动反馈实战:用navigator.vibrate打造buzz模块

做移动端H5的时候&#xff0c;我攒了一个叫“buzz”的小模块。项目代号就一个字&#xff0c;buzz&#xff0c;干的事也跟这个单词的本意一样&#xff1a;让手机在合适的时机“嗡”一下。说白了就是调用浏览器自带的振动接口&#xff0c;给页面加上真实的触觉反馈。这个需求一开…

作者头像 李华
网站建设 2026/9/29 5:48:53

SQL Server 判断table是否在使用replicate功能

--table是否在使用replicate功能 publication: select name from sys.tables where is_published1 order by 1subscription: SELECT distinct [publisher],[publisher_db],[publication],[article]FROM [db_name].[dbo].[MSreplication_objects]where articletable_nameorder b…

作者头像 李华
网站建设 2026/9/29 5:48:41

superpowers工作流:把AI编码助手变成主动执行引擎

1. 为什么需要一套“编码超能力”工作流我第一次接触“superpowers”这个概念&#xff0c;是被团队里一个极客同事安利的。他当时正在调一个几十万行的历史遗留Java服务&#xff0c;需求是从一堆纠缠不清的状态机里剥离出独立鉴权模块。这种活儿放在以前&#xff0c;光是梳理调…

作者头像 李华
网站建设 2026/9/29 5:48:12

MindSpore中Transformers模型的在线监控回调实战

1. 项目概述&#xff1a;为什么训练过程不能“黑箱”运行&#xff1f; 在MindSpore生态里跑一个Transformers模型&#xff0c;比如BERT、ViT或者ChatGLM的轻量变体&#xff0c;很多人卡在第一步——不是模型搭不出来&#xff0c;而是训练跑起来之后&#xff0c;心里没底。Loss…

作者头像 李华