news 2026/9/24 15:13:53

Humanizer 时间跨度人性化策略接口 ITimeSpanHumanizeStrategy 深度解析:方法签名、参数语义与自定义策略实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Humanizer 时间跨度人性化策略接口 ITimeSpanHumanizeStrategy 深度解析:方法签名、参数语义与自定义策略实战
  • 开发工具

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

ITimeSpanHumanizeStrategy是 Humanizer 中负责把System.TimeSpan转换成人类可读文本的核心抽象。本文以该接口的 API 参考文档为主体,结合 接口源码、默认实现、Configurator注册机制与测试用例,完整讲解 9 个参数的语义、策略的接入与替换方式,并给出自定义策略的实战写法,帮助你掌握 Humanizer 时间跨度人性化能力的扩展点。

接口定位:策略模式下的 TimeSpan 人性化抽象

Humanizer 把"把TimeSpan变成人话"这一行为抽象成了策略接口,其设计意图在 ITimeSpanHumanizeStrategy.cs 的注释中写得很明确:

Defines a strategy for convertingTimeSpanvalues into human-readable text.

接口定义本身非常精简,只有一个方法:

namespace Humanizer; public interface ITimeSpanHumanizeStrategy { string Humanize( TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo? culture, TimeUnit maxUnit, TimeUnit minUnit, string? collectionSeparator, bool toWords, bool toSymbols); }

之所以采用策略模式,是因为"人性化"是一个高度可定制的行为:同样的TimeSpan,在不同文化(culture)、不同精度要求、不同输出风格(数字/单词/符号)下,结果差异巨大。把算法封装进可替换的策略对象,既保证了内置默认行为的稳定性,又为使用方预留了完全自定义的扩展通道。

Humanize 方法签名与 9 个参数详解

接口的核心方法Humanize接收一个TimeSpan和 9 个控制参数,返回人性化后的字符串。以下逐一说明每个参数的语义(与 API 参考文档 一致,并补充仓库源码中的默认值与取值范围)。

timeSpan:待转换的时间跨度

要人性化的TimeSpan值本身。正值、负值、零值都会进入策略处理逻辑。

precision:返回的最大时间单位个数

类型int,含义是"最多返回多少个时间单位"。默认值为 1,即只返回最大单位。例如默认精度下,new TimeSpan(2, 3, 4, 5)只输出类似2 days的结果;把精度提高到 2,才会追加到2 days 3 hours

countEmptyUnits:空单位是否计入 precision

类型bool。控制"值为 0 的中间单位"是否占用precision配额。这一点在 TimeSpanHumanizeExtensions.cs 的扩展方法文档中有更精确的表述:

Controls whether empty time units should be counted towards maximum number of time units. Leading empty time units never count.

注意一个重要细节:前导(leading)的空单位永远不计入countEmptyUnits只影响非前导位置的零值单位是否占用精度配额。仓库测试 TimeSpanWithPrecisionAndCountingEmptyUnits 专门覆盖了这一行为差异。

culture:本地化文化

类型CultureInfo?。为null时使用当前线程文化(current culture)。传入明确文化(例如new CultureInfo("zh-CN")"en-US")可以让同一TimeSpan在不同语言环境下输出本地化的单位名称。测试 CanSpecifyCultureExplicitly 与 SymbolsUseTheRequestedCulture 验证了显式指定文化的效果。

maxUnit 与 minUnit:输出单位的上下界

两者类型都是TimeUnitTimeUnit枚举定义在 TimeUnit.cs,共 8 个取值,按数值从小到大排列:

枚举值含义
Millisecond毫秒
Second
Minute分钟
Hour小时
Day
Week
Month
Year

maxUnit限定输出中允许出现的最大单位,minUnit限定最小单位。TimeSpanHumanizeExtensions的默认值是maxUnit = TimeUnit.WeekminUnit = TimeUnit.Millisecond。扩展方法 XML 文档还补充了一个重要近似说明(TimeSpanHumanizeExtensions.cs):

The default value isTimeUnit.Week. The time unitsTimeUnit.MonthandTimeUnit.Yearwill give approximations for time spans bigger 30 days by calculating with 365.2425 days a year and 30.4369 days a month.

也就是说,一旦把maxUnit放宽到MonthYear,超过 30 天的跨度将按近似值换算(一年按 365.2425 天、一个月按约 30.4369 天)。这些常量定义在扩展类顶部:

const double DaysInAYear = 365.2425; // 公历平均年长 const double DaysInAMonth = DaysInAYear / 12;

测试 Months、Years 与 MonthAndYearRangesIncludeWeeks 对这些换算行为做了大量断言。

collectionSeparator:多个时间部分的连接符

类型string?。当结果包含多个时间单位(例如2 days 3 hours)时,各部分之间用什么分隔。为null时回退到当前文化默认的 collection formatter。扩展方法层的默认值是", "。测试 TimeSpanWithPrecisionAndAlternativeCollectionFormatter 验证了自定义分隔符(包括语言本地化分隔符)的效果。

toWords:数字是否用单词呈现

类型bool。为true时数字以单词形式输出,例如one day而不是1 day。扩展方法文档示例即E.g. one day。测试 TimeSpanWithNumbersConvertedToWords 覆盖此路径。

toSymbols:单位是否用符号呈现

类型bool。为true时时间单位渲染为本地化符号(如dhms),而非完整单词。该参数主要由HumanizeToSymbols系列扩展方法置为true传入。相关测试包括 CanUseLocalizedSymbolsWithPrecision、SymbolZeroUsesMinimumUnit 与 SymbolsUseTheRequestedCulture。

返回值

返回string,即人性化后的时间跨度文本。各参数组合最终都会归结为这个字符串结果。

派生类型:从默认策略到语法格感知策略

API 参考文档列出了两个派生关系,它们在仓库源码中对应如下:

DefaultTimeSpanHumanizeStrategy:内置默认实现

定义在 DefaultTimeSpanHumanizeStrategy.cs,是Configurator中注册的默认策略(见下文)。它的Humanize方法直接委托给扩展类内部的TimeSpanHumanizeExtensions.DefaultHumanize,真正的算法核心在DefaultHumanizeCore(TimeSpanHumanizeExtensions.cs)。此外该类还提供了HumanizeWithFractionalSeconds虚方法,用于秒级以下小数位的输出。

IGrammaticalCaseTimeSpanHumanizeStrategy:语法格感知扩展

定义在 IGrammaticalCaseTimeSpanHumanizeStrategy.cs,它继承自ITimeSpanHumanizeStrategy,额外增加一个带GrammaticalCase grammaticalCase参数的Humanize重载。其注释说明:

Optionally extends a time-span humanization strategy with grammatical-case support. ExistingITimeSpanHumanizeStrategyimplementations remain valid for existing duration APIs but cannot serviceHumanizeWithCase.

GrammaticalCase枚举定义在 GrammaticalCase.cs,包含NominativeGenitiveDativeAccusativeInstrumentalPrepositionalAblativeComitativeErgativeLocativeObliquePartitiveVocative等 30 余种语法格。这对于俄语、巴斯克语、芬兰语等格变化丰富的语言尤为重要——同一个时间单位在不同语法格下需要不同的词形。

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

  1. DefaultTimeSpanHumanizeStrategy显式实现了IGrammaticalCaseTimeSpanHumanizeStrategy.Humanize,且对非 Humanizer 程序集的自定义子类抛出NotSupportedException(DefaultTimeSpanHumanizeStrategy.cs),要求自定义策略必须显式实现该接口才能获得语法格支持;
  2. 扩展方法HumanizeWithCaseConfigurator.TimeSpanHumanizeStrategy不是IGrammaticalCaseTimeSpanHumanizeStrategy时同样抛出NotSupportedException(TimeSpanHumanizeExtensions.cs)。

策略接入点:Configurator 注册与扩展方法调用链

策略的注册中心是Configurator。在 Configurator.cs 中:

public static ITimeSpanHumanizeStrategy TimeSpanHumanizeStrategy { get; set; } = new DefaultTimeSpanHumanizeStrategy();

所有TimeSpanHumanizeExtensions的公开扩展方法(HumanizeHumanizeToSymbolsHumanizeWithCaseHumanizeWithFractionalSeconds等)最终都会路由到Configurator.TimeSpanHumanizeStrategy.Humanize(...)。以最常用的重载为例(TimeSpanHumanizeExtensions.cs):

public static string Humanize( this TimeSpan timeSpan, int precision = 1, CultureInfo? culture = null, TimeUnit maxUnit = TimeUnit.Week, TimeUnit minUnit = TimeUnit.Millisecond, string? collectionSeparator = ", ", bool toWords = false) => Configurator.TimeSpanHumanizeStrategy.Humanize( timeSpan, precision, false, culture, maxUnit, minUnit, collectionSeparator, toWords, false);

从调用链可以清晰看到公开重载与接口 9 参数签名之间的映射关系:countEmptyUnits在无参版本固定为falsetoSymbols固定为false,而HumanizeToSymbols则把toSymbols置为true。策略内部按maxUnit → minUnit从大到小分解时间跨度——TimeUnits静态数组正是Enum.GetValues<TimeUnit>()的反转结果(TimeSpanHumanizeExtensions.cs),即从YearMillisecond依次尝试。

实战:编写并注册自定义策略

基于接口定义,可以推断出自定义策略的标准实现骨架。例如,实现一个把所有单位名统一转为小写的策略:

public sealed class LowercaseTimeSpanHumanizeStrategy : ITimeSpanHumanizeStrategy { public string Humanize( TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo? culture, TimeUnit maxUnit, TimeUnit minUnit, string? collectionSeparator, bool toWords, bool toSymbols) => Humanizer.TimeSpanHumanizeExtensions .DefaultHumanize(timeSpan, precision, countEmptyUnits, culture, maxUnit, minUnit, collectionSeparator, toWords, toSymbols) .ToLowerInvariant(); }

说明:DefaultHumanize在仓库中标记为internal(TimeSpanHumanizeExtensions.cs),因此上面的示例是概念性骨架,用于展示接口职责;实际自定义策略时,要么自行实现分解算法,要么基于Configurator.TimeSpanHumanizeStrategy当前实例做包装委托。

注册策略只需替换Configurator属性。仓库测试提供了标准的"保存—替换—恢复"模式(参见 FractionalTimeSpanHumanizeTests.cs):

var originalStrategy = Configurator.TimeSpanHumanizeStrategy; try { Configurator.TimeSpanHumanizeStrategy = new MyCustomStrategy(); // 在此断言 Humanize 输出 } finally { Configurator.TimeSpanHumanizeStrategy = originalStrategy; }

注册之后,所有TimeSpan扩展方法(HumanizeHumanizeToSymbolsHumanizeWithCase等)都会自动走新策略。需要特别留意的是:如果你的自定义策略要支持HumanizeWithCase,必须显式实现IGrammaticalCaseTimeSpanHumanizeStrategy接口;若还需要支持HumanizeWithFractionalSeconds,则应实现IFractionalTimeSpanHumanizeStrategy(定义于 IFractionalTimeSpanHumanizeStrategy.cs,扩展方法在 TimeSpanHumanizeExtensions.cs 处做了is IFractionalTimeSpanHumanizeStrategy的类型探测)。由于策略是全局单点配置,多线程场景下应避免在运行期频繁替换,或在替换后尽快恢复。

测试验证与行为保证

仓库测试为策略行为提供了丰富的契约验证,主要集中在 TimeSpanHumanizeTests.cs 与 FractionalTimeSpanHumanizeTests.cs,值得关注的核心用例:

  • DefaultStrategyPreservesExistingOutputs:默认策略必须保持既有输出不变,防止回归;
  • TimeSpanWithPrecisionAndCountingEmptyUnits:验证countEmptyUnits对精度配额的影响;
  • TimeSpanWithMinAndMaxUnits_DoesNotReportExcessiveTime:验证单位上下界的约束;
  • GrammaticalCaseAppliesToEveryPart 与 BasqueUsesAbsolutiveAsItsCitationCase:验证语法格作用于输出的每个部分;
  • FractionalTimeSpanHumanizeTests.cs 中的策略替换用例:验证Configurator.TimeSpanHumanizeStrategy可替换、可恢复,以及自定义策略与小数秒 API 的组合行为。

这些测试既是自定义策略时的行为参照,也是确认"策略替换后扩展方法路由是否正确"的可靠证据。

小结

ITimeSpanHumanizeStrategy是 Humanizer 时间跨度人性化能力的核心扩展点:9 个参数完整覆盖了精度、空单位计数、文化、单位上下界、分隔符、数字/单词/符号输出等全部维度;Configurator.TimeSpanHumanizeStrategy提供了全局替换入口;DefaultTimeSpanHumanizeStrategy保证开箱即用的默认行为;IGrammaticalCaseTimeSpanHumanizeStrategyIFractionalTimeSpanHumanizeStrategy则在默认能力之上补充了语法格与小数秒的进阶支持。掌握这个接口,就能在 Humanizer 的时间跨度人性化输出上实现任意自定义策略。

相关源码与文档路径

  • API 参考文档:Humanizer.ITimeSpanHumanizeStrategy.md
  • 接口定义:ITimeSpanHumanizeStrategy.cs
  • 默认实现:DefaultTimeSpanHumanizeStrategy.cs
  • 语法格扩展接口:IGrammaticalCaseTimeSpanHumanizeStrategy.cs
  • 公开扩展方法与核心算法:TimeSpanHumanizeExtensions.cs
  • 策略注册中心:Configurator.cs
  • 时间单位枚举:TimeUnit.cs
  • 语法格枚举:GrammaticalCase.cs
  • 行为测试:TimeSpanHumanizeTests.cs、FractionalTimeSpanHumanizeTests.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
点击查看免费下载
上一篇:Improve YouTube!:终极YouTube增强扩展,90+功能全面解析
下一篇:解剖 Claude Code 的 Plan 子代理提示词:一个只读“软件架构师”如何产出实施计划

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

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