☰
Humanizer 相对时间人性化:DefaultDateTimeHumanizeStrategy 默认策略源码级解析
2026/9/25 2:38:48 网站建设 项目流程
  • 开发工具

【免费下载链接】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 的DefaultDateTimeHumanizeStrategy是 .NET 平台下将两个DateTime之间的时间距离转换为自然语言(如 "2 hours ago"、"tomorrow"、"one month from now")的默认计算策略。本文以该策略的 API 参考文档为核心,结合 src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs 及配套算法源码、配置入口和测试用例,完整讲解它的类结构、方法签名、底层分级算法、时态判断与本地化机制,并给出可直接运行的实战示例。读完本文,你将能掌握DateTime.Humanize()的默认输出规则、替换为自定义策略的方法,以及通过测试数据验证输出结果的具体方式。

类定位:Humanizer 日期人性化的策略模式入口

在 Humanizer 中,"人性化时间距离"(distance of time in words)并不是硬编码在扩展方法里的,而是通过策略模式实现,DefaultDateTimeHumanizeStrategy就是这一体系的默认实现。

public class DefaultDateTimeHumanizeStrategy : Humanizer.DateTimeHumanizeStrategy.IDateTimeHumanizeStrategy

类声明要点:

  • 命名空间:Humanizer.DateTimeHumanizeStrategy;
  • 继承链:System.Object→DefaultDateTimeHumanizeStrategy;
  • 实现接口:IDateTimeHumanizeStrategy,该接口只声明了一个方法string Humanize(DateTime input, DateTime comparisonBase, CultureInfo? culture)。

接口的设计目标(见 IDateTimeHumanizeStrategy.cs)是:允许开发者实现自己的策略,并通过Configurator.DateTimeHumanizeStrategy把它挂接到DateTime.Humanize上。也就是说,DefaultDateTimeHumanizeStrategy只是 Humanizer 提供给开箱即用的"默认"答案,整个计算链路是开放的、可替换的。

策略被挂接的位置在 Configurator.cs,默认值正是本类:

public static IDateTimeHumanizeStrategy DateTimeHumanizeStrategy { get; set; } = new DefaultDateTimeHumanizeStrategy();

该属性属于Humanizer.Configuration.Configurator静态类,因此在实际应用中,可以通过一行代码全局替换人性化策略,例如:

Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(0.75);

Humanize 方法:签名、参数与返回值

文档核心 API 是Humanize(DateTime, DateTime, CultureInfo)方法,官方定义为"计算两个给定日期之间的时间距离(用文字表达)"。

public string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture);

参数说明

参数类型含义
inputSystem.DateTime要被人性化描述的日期,即"目标时间点"
comparisonBaseSystem.DateTime比较基准日期,即"当前时间点"
cultureSystem.Globalization.CultureInfo用于本地化输出的区域性信息,例如en-US、zh-CN、ru-RU

返回值

System.String:一段本地化的自然语言描述,例如"one year ago"、"in 2 weeks"。

实现:一行委托给核心算法

DefaultDateTimeHumanizeStrategy的源码极其简洁,它本身不包含任何计算逻辑,而是把工作委托给 DateTimeHumanizeAlgorithms.DefaultHumanize:

public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo? culture) => DateTimeHumanizeAlgorithms.DefaultHumanize(input, comparisonBase, culture);

真正的时间距离换算、时态判定、单位取舍,全部发生在DateTimeHumanizeAlgorithms这个静态算法类中。

完整调用链:从扩展方法到最终输出

在实际使用中,开发者通常不会直接调用策略的Humanize,而是通过扩展方法触发。整条调用链如下:

  1. 入口:DateHumanizeExtensions.cs 中的DateTime.Humanize()扩展方法:
    public static string Humanize(this DateTime input, bool? utcDate = null, DateTime? dateToCompareAgainst = null, CultureInfo? culture = null)

    它负责确定comparisonBase(默认取DateTime.UtcNow),并根据utcDate参数将基准统一为 UTC 或本地时间,然后调用Configurator.DateTimeHumanizeStrategy.Humanize(input, comparisonBase, culture);

  2. 策略分发:Configurator.DateTimeHumanizeStrategy当前指向DefaultDateTimeHumanizeStrategy;
  3. 算法执行:DefaultDateTimeHumanizeStrategy.Humanize委托给DateTimeHumanizeAlgorithms.DefaultHumanize;
  4. 本地化输出:算法内部通过Configurator.GetFormatter(culture)取得 IFormatter 实例,调用formatter.DateHumanize(TimeUnit, Tense, int)拼出最终文本。

值得注意的是扩展方法还提供了两个便捷重载(见 DateHumanizeExtensions.cs):

  • DateTime?可空重载:输入为null时返回本地化的 "never"(由formatter.DateHumanize_Never()提供);
  • 显式传入dateToCompareAgainst和culture,可在不依赖系统时钟的情况下做确定性测试(测试代码正是这样做的)。

默认算法逐级拆解:阈值、时态与单位选择

DateTimeHumanizeAlgorithms.DefaultHumanize是整篇文章的核心。它的第一步是计算"时态"与"时间跨度":

var tense = input > comparisonBase ? Tense.Future : Tense.Past; var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks));
  • 时态:input晚于comparisonBase判定为Tense.Future(未来),否则为Tense.Past(过去)。两个枚举值的语义见 Tense.cs:Future 输出类似 "in 2 days",Past 输出类似 "2 days ago";
  • 时间差:通过Ticks差取绝对值构造TimeSpan,因此算法只关心"距离"的大小,不关心正负方向;
  • 月份特殊判定:sameMonth用于判断input与comparisonBase是否恰好相差一个月(考虑未来/过去方向),这影响 28~30 天区间内的输出归属。

随后进入由小到大、逐级匹配的阈值判断(见 DateTimeHumanizeAlgorithms.cs)。下表汇总了完整阈值与对应输出单位:

区间条件输出单位与数量典型输出(en-US,Past)
ts.TotalMilliseconds < 500Millisecond,0"now"
ts.TotalSeconds < 60Second,ts.Seconds"10 seconds ago"
ts.TotalSeconds < 120Minute,1"a minute ago"
ts.TotalMinutes < 60Minute,ts.Minutes"44 minutes ago"
ts.TotalMinutes < 90Hour,1"an hour ago"
ts.TotalHours < 24Hour,ts.Hours"10 hours ago"
ts.TotalHours < 48Day,days"yesterday"(跨日)或 "2 days ago"
ts.TotalDays < 7Day,ts.Days"6 days ago"
ts.TotalDays < 28Week,ts.Days / 7"one week ago"、"2 weeks ago"
28 ≤ TotalDays < 30若sameMonth为 Month,1,否则 Day"one month ago" 或按天
TotalDays < 345Month,floor(TotalDays / 29.5)"10 months ago"
其余Year,floor(TotalDays / 365)(至少 1)"one year ago"

几个值得注意的算法细节:

  • "一小时"边界:90 分钟以内一律输出 "an hour" 而非 "1 hour",这与英语本地化习惯一致;
  • "昨天/明天"判定:TotalHours < 48分支使用的是days = Math.Abs((input.Date - comparisonBase.Date).Days),即按日历日差值而非按小时数计算,所以跨天 1 天输出 "yesterday"/"tomorrow",而不是 "24 hours ago";
  • 28~30 天的月份边界:sameMonth成立时输出 "one month",否则输出 "N days"。测试 TwentyEightDaysUsesCalendarMonth 专门验证了这一点:从 2023-03-01 往前 28 天输出 "one month ago",从 2023-02-01 往后 28 天输出 "one month from now";
  • 年度下限:TotalDays / 365计算出的years为 0 时强制置 1,避免出现 "0 years"。

最终输出统一调用:

formatter.DateHumanize(TimeUnit.Year, tense, years);

其中TimeUnit枚举(Millisecond / Second / Minute / Hour / Day / Week / Month / Year)见 TimeUnit.cs,数量词的复数形式("one second" vs "2 seconds")由各语言 formatter 内部处理。

本地化:culture 如何影响最终文案

culture参数在算法中并不参与数值计算,而是决定选用哪个语言的 formatter。算法内部通过Configurator.GetFormatter(culture)(见 Configurator.cs)从FormatterRegistry解析出对应文化的IFormatter,再由它把(TimeUnit, Tense, count)三元组渲染成本地语言的自然语句。

这一机制可以从测试得到直观印证。CanSpecifyCultureExplicitly 展示了同一组数值在不同文化下的输出:

[InlineData(1, TimeUnit.Year, Tense.Future, "en-US", "one year from now")] [InlineData(40, TimeUnit.Second, Tense.Past, "ru-RU", "40 секунд назад")] [InlineData(2, TimeUnit.Day, Tense.Past, "sv-SE", "för 2 dagar sedan")] [InlineData(2, TimeUnit.Week, Tense.Future, "de-DE", "in 2 Wochen")]

如果调用时不传culture(传null),Humanizer 将使用当前线程的CurrentCulture。完整的本地化文案由各语言的.yml语言资源文件维护(见 src/Humanizer/Locales 下的en.yml、ru.yml、de.yml等),并由 SourceGenerator 在编译期生成对应的 Formatter 类型。

与 PrecisionDateTimeHumanizeStrategy 的对比

理解默认策略的最佳参照系是同接口的另一个实现 PrecisionDateTimeHumanizeStrategy:

public class PrecisionDateTimeHumanizeStrategy(double precision = .75) : IDateTimeHumanizeStrategy { public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo? culture) => DateTimeHumanizeAlgorithms.PrecisionHumanize(input, comparisonBase, precision, culture); }

两者差异集中在两点:

  • 默认策略(本主题):用一系列固定阈值直接"截断"到最合适的单位,简单直观、输出稳定,是Configurator的默认选择;
  • 精度策略:接受一个precision(默认 0.75)参数,在单位进位时做近似舍入(如毫秒数>= 999 * precision才进位到秒),适合需要微调边界行为的场景。

测试辅助类 DateHumanize.cs 会按precision是否传入在这两个策略之间切换,正是两者在测试体系中可互换的证明。在.NET 6+目标框架下,算法类还为DateOnly、TimeOnly提供了对应重载(见 DateTimeHumanizeAlgorithms.cs),行为与DateTime版本保持一致。

实战:使用与预期输出速查

引入命名空间using Humanizer;后即可直接调用:

using Humanizer; var now = DateTime.UtcNow; now.AddSeconds(-10).Humanize(); // "10 seconds ago" now.AddMinutes(45).Humanize(); // "45 minutes from now" now.AddHours(-23).Humanize(); // "23 hours ago" now.AddDays(-1).Humanize(); // "yesterday" now.AddDays(1).Humanize(); // "tomorrow" now.AddDays(-13).Humanize(); // "one week ago" now.AddDays(-32).Humanize(); // "one month ago" now.AddDays(-400).Humanize(); // "one year ago" // 可空日期与显式文化 DateTime? never = null; never.Humanize(); // "never" now.AddMonths(-10).Humanize(culture: new System.Globalization.CultureInfo("ru-RU"));

以上英文输出均可在 DateHumanizeDefaultStrategyTests.cs 的 Theory 数据中找到对应断言,例如:

  • SecondsAgo:60 秒 → "a minute ago"(L4-L10);
  • HoursAgo:24 小时 → "yesterday"(L42-L48);
  • DaysAgo:7/13 天 → "one week ago",32 天 → "one month ago"(L70-L79);
  • MonthsAgo:12 个月 → "one year ago"(L102-L108);
  • Now:0 差值 → "now"(L130-L132)。

总结

DefaultDateTimeHumanizeStrategy是 Humanizer 日期人性化的"标准答案":它实现IDateTimeHumanizeStrategy接口,把全部计算委托给DateTimeHumanizeAlgorithms.DefaultHumanize,通过一套由毫秒到年的分级阈值把TimeSpan距离映射为最合适的TimeUnit,再借助文化相关的IFormatter输出本地化文案。理解它等于理解了DateTime.Humanize()的全部默认行为——包括 "yesterday/tomorrow" 的日历日判定、28~30 天的月份边界特判、一周内的周单位折算,以及通过Configurator.DateTimeHumanizeStrategy替换为自定义或PrecisionDateTimeHumanizeStrategy的扩展路径。若需进一步研究接口契约,可参考 IDateTimeHumanizeStrategy 文档,或直接阅读 DateTimeHumanizeAlgorithms.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
点击查看免费下载
上一篇:NiGui未来路线图:即将到来的macOS支持与新特性预览
下一篇:Awesome MCP Servers 项目教程

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

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

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

立即咨询