- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
ITimeSpanHumanizeStrategy是 Humanizer 中负责把System.TimeSpan转换成人类可读文本的核心抽象。本文以该接口的 API 参考文档为主体,结合 接口源码、默认实现、Configurator注册机制与测试用例,完整讲解 9 个参数的语义、策略的接入与替换方式,并给出自定义策略的实战写法,帮助你掌握 Humanizer 时间跨度人性化能力的扩展点。
接口定位:策略模式下的 TimeSpan 人性化抽象
Humanizer 把"把TimeSpan变成人话"这一行为抽象成了策略接口,其设计意图在 ITimeSpanHumanizeStrategy.cs 的注释中写得很明确:
Defines a strategy for converting
TimeSpanvalues 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:输出单位的上下界
两者类型都是TimeUnit。TimeUnit枚举定义在 TimeUnit.cs,共 8 个取值,按数值从小到大排列:
| 枚举值 | 含义 |
|---|---|
Millisecond | 毫秒 |
Second | 秒 |
Minute | 分钟 |
Hour | 小时 |
Day | 天 |
Week | 周 |
Month | 月 |
Year | 年 |
maxUnit限定输出中允许出现的最大单位,minUnit限定最小单位。TimeSpanHumanizeExtensions的默认值是maxUnit = TimeUnit.Week、minUnit = TimeUnit.Millisecond。扩展方法 XML 文档还补充了一个重要近似说明(TimeSpanHumanizeExtensions.cs):
The default value is
TimeUnit.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放宽到Month或Year,超过 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时时间单位渲染为本地化符号(如d、h、m、s),而非完整单词。该参数主要由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. Existing
ITimeSpanHumanizeStrategyimplementations remain valid for existing duration APIs but cannot serviceHumanizeWithCase.
GrammaticalCase枚举定义在 GrammaticalCase.cs,包含Nominative、Genitive、Dative、Accusative、Instrumental、Prepositional、Ablative、Comitative、Ergative、Locative、Oblique、Partitive、Vocative等 30 余种语法格。这对于俄语、巴斯克语、芬兰语等格变化丰富的语言尤为重要——同一个时间单位在不同语法格下需要不同的词形。
值得注意的两个实现细节:
DefaultTimeSpanHumanizeStrategy显式实现了IGrammaticalCaseTimeSpanHumanizeStrategy.Humanize,且对非 Humanizer 程序集的自定义子类抛出NotSupportedException(DefaultTimeSpanHumanizeStrategy.cs),要求自定义策略必须显式实现该接口才能获得语法格支持;- 扩展方法
HumanizeWithCase在Configurator.TimeSpanHumanizeStrategy不是IGrammaticalCaseTimeSpanHumanizeStrategy时同样抛出NotSupportedException(TimeSpanHumanizeExtensions.cs)。
策略接入点:Configurator 注册与扩展方法调用链
策略的注册中心是Configurator。在 Configurator.cs 中:
public static ITimeSpanHumanizeStrategy TimeSpanHumanizeStrategy { get; set; } = new DefaultTimeSpanHumanizeStrategy();所有TimeSpanHumanizeExtensions的公开扩展方法(Humanize、HumanizeToSymbols、HumanizeWithCase、HumanizeWithFractionalSeconds等)最终都会路由到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在无参版本固定为false,toSymbols固定为false,而HumanizeToSymbols则把toSymbols置为true。策略内部按maxUnit → minUnit从大到小分解时间跨度——TimeUnits静态数组正是Enum.GetValues<TimeUnit>()的反转结果(TimeSpanHumanizeExtensions.cs),即从Year到Millisecond依次尝试。
实战:编写并注册自定义策略
基于接口定义,可以推断出自定义策略的标准实现骨架。例如,实现一个把所有单位名统一转为小写的策略:
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扩展方法(Humanize、HumanizeToSymbols、HumanizeWithCase等)都会自动走新策略。需要特别留意的是:如果你的自定义策略要支持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保证开箱即用的默认行为;IGrammaticalCaseTimeSpanHumanizeStrategy与IFractionalTimeSpanHumanizeStrategy则在默认能力之上补充了语法格与小数秒的进阶支持。掌握这个接口,就能在 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
相关推荐
Humanizer 小数秒时间跨度人性化策略:IFractionalTimeSpanHumanizeStrategy 接口深度解析与自定义实现指南
Humanizer 小数秒时间跨度人性化策略:IFractionalTimeSpanHumanizeStrategy 接口深度解析与自定义实现指南 导读 当 T
开发工具Humanizer 中 IDateOnlyHumanizeStrategy 接口全解析:自定义 DateOnly.Humanize 的人性化时间策略
Humanizer 中 IDateOnlyHumanizeStrategy 接口全解析:自定义 DateOnly.Humanize 的人性化时间策略 本篇指南围
开发工具Humanizer 中 IDateTimeOffsetHumanizeStrategy 接口详解:自定义 DateTimeOffset.Humanize 日期人性化策略
Humanizer 中 IDateTimeOffsetHumanizeStrategy 接口详解:自定义 DateTimeOffset.Humanize 日期人
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考