- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
导读
当TimeSpan的精度下沉到亚秒级别(如0.5 seconds、1.234 seconds)时,Humanizer 内置的整数时间单位输出就无能为力了。本文聚焦于 IFractionalTimeSpanHumanizeStrategy 接口——Humanizer 为时间跨度人性化(TimeSpan humanization)扩展出的小数秒支持抽象层,逐一拆解其方法签名、九个参数的语义边界、默认实现的路由逻辑,并给出可落地的自定义策略示例。读完本文,你将掌握如何配置、验证乃至替换 Humanizer 的小数秒时间格式化行为。
接口在时间跨度人性化体系中的定位
Humanizer 把"把TimeSpan变成人话"这件事抽象为可替换的策略(Strategy),对外入口是 TimeSpanHumanizeExtensions.cs 中一系列Humanize扩展方法,它们统一委托给Configurator.TimeSpanHumanizeStrategy完成实际格式化。从 Configurator.cs 可以看到默认策略是DefaultTimeSpanHumanizeStrategy:
public static ITimeSpanHumanizeStrategy TimeSpanHumanizeStrategy { get; set; } = new DefaultTimeSpanHumanizeStrategy();普通Humanize系列以毫秒(Millisecond)为最小输出单位;而IFractionalTimeSpanHumanizeStrategy的出现,是为了让输出能以秒(Second)为最小单位,并携带最多 7 位的小数部分。官方 API 文档对其的定位是:"Extends a time-span humanization strategy with fractional-second support."(为时间跨度人性化策略扩展小数秒支持)。
接口定义与完整方法签名
接口本体定义在 IFractionalTimeSpanHumanizeStrategy.cs,全部代码即一个方法:
namespace Humanizer; public interface IFractionalTimeSpanHumanizeStrategy { string HumanizeWithFractionalSeconds( TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo? culture, TimeUnit maxUnit, string? collectionSeparator, int maxFractionalDigits, MidpointRounding roundingMode, bool toSymbols); }方法的语义是:将TimeSpan转换为以秒为最小单位的人性化文本。TimeUnit枚举定义于 TimeUnit.cs,取值依次为Millisecond、Second、Minute、Hour、Day、Week、Month、Year。
九个参数逐一拆解
| 参数 | 类型 | 语义 |
|---|---|---|
timeSpan | TimeSpan | 待人性化的时间跨度。 |
precision | int | 返回的最大时间单位数量(如precision = 2最多输出两个单位)。 |
countEmptyUnits | bool | 空时间单位是否计入precision。为true时,即使某单位为 0 也占用一个精度名额。 |
culture | CultureInfo? | 使用的区域性;传null时使用当前文化(CultureInfo.CurrentCulture)。 |
maxUnit | TimeUnit | 允许输出的最大时间单位(如TimeUnit.Hour则不会出现天/周等更大单位)。 |
collectionSeparator | string? | 组合多个时间部分时使用的分隔符;传null时使用该文化的默认 collection formatter 规则。 |
maxFractionalDigits | int | 小数秒的最大位数,取值范围0 到 7(对应 tick 精度,1 tick = 100 纳秒)。 |
roundingMode | MidpointRounding | 中点舍入模式,小数秒功能仅支持ToEven与AwayFromZero两种。 |
toSymbols | bool | 时间单位是否渲染为本地化符号(如s、min),而非完整单词。 |
返回值string即最终的人性化文本。
扩展方法入口:谁在调用这个接口
虽然接口本身面向策略实现者,但日常使用几乎总是通过扩展方法触发。在 TimeSpanHumanizeExtensions.cs 中提供了两组共四个入口:
// 单词形式,秒作为最小单位 public static string HumanizeWithFractionalSeconds( this TimeSpan timeSpan, int precision, int maxFractionalDigits, MidpointRounding roundingMode, CultureInfo? culture, TimeUnit maxUnit, string? collectionSeparator = ", ") // 带 countEmptyUnits 的重载 public static string HumanizeWithFractionalSeconds( this TimeSpan timeSpan, int precision, bool countEmptyUnits, int maxFractionalDigits, MidpointRounding roundingMode, CultureInfo? culture, TimeUnit maxUnit, string? collectionSeparator = ", ") // 符号形式 public static string HumanizeToSymbolsWithFractionalSeconds( this TimeSpan timeSpan, ...) // 符号形式 + countEmptyUnits 重载 public static string HumanizeToSymbolsWithFractionalSeconds( this TimeSpan timeSpan, ...)核心路由逻辑:三层回退
所有入口最终汇聚到私有的HumanizeWithFractionalSecondsCore(TimeSpanHumanizeExtensions.cs),它决定了当前配置的策略如何处理小数秒:
- 若配置的策略恰好是
DefaultTimeSpanHumanizeStrategy:直接强转调用其HumanizeWithFractionalSeconds虚方法,走内置的小数秒实现(DefaultHumanizeWithFractionalSeconds)。 - 若配置的策略实现了
IFractionalTimeSpanHumanizeStrategy:调用接口方法,即自定义策略全权接管。 - 否则(普通策略):先把整个时间跨度按
maxFractionalDigits+roundingMode舍入;如果舍入后仍存在可见的小数秒,抛出InvalidOperationException,提示"当前配置的ITimeSpanHumanizeStrategy不支持小数秒,请实现IFractionalTimeSpanHumanizeStrategy以处理真正的小数结果";如果没有小数秒,则以minUnit = TimeUnit.Second回退到普通Humanize路径。
可见,实现该接口是让任意自定义策略支持小数秒的官方契约,也是唯一的完整接管点。
默认实现:DefaultTimeSpanHumanizeStrategy
默认策略类定义于 DefaultTimeSpanHumanizeStrategy.cs,它同时实现IGrammaticalCaseTimeSpanHumanizeStrategy(支持格变化感知的时长)并声明HumanizeWithFractionalSeconds为virtual,允许子类覆写:
public virtual string HumanizeWithFractionalSeconds( TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo? culture, TimeUnit maxUnit, string? collectionSeparator, int maxFractionalDigits, MidpointRounding roundingMode, bool toSymbols) => TimeSpanHumanizeExtensions.DefaultHumanizeWithFractionalSeconds( timeSpan, precision, countEmptyUnits, culture, maxUnit, collectionSeparator, maxFractionalDigits, roundingMode, toSymbols);其内部实现(DefaultHumanizeWithFractionalSeconds,见 TimeSpanHumanizeExtensions.cs)分几步走:
- 参数校验:
maxFractionalDigits必须在 0–7;roundingMode只能是ToEven/AwayFromZero;maxUnit必须在Second到Year之间(注意:不能低于Second,因为秒就是该功能的最小单位)。 - 整体舍入:
RoundToFractionalSecondPrecision基于decimal运算,按10^(7 - maxFractionalDigits)tick 的量子粒度做整个量级的舍入(而非逐单位舍入),并从TimeSpan两端极值测试可以看出其刻意规避了符号位与溢出的边界问题。 - 分解部件:
CreateFractionalSecondParts按Week → Second顺序切分,precision控制部件数量上限,countEmptyUnits决定 0 值单位是否占用精度名额。 - 格式化部件:非秒单位走常规
FormatTimePart;秒单位若是整数且落在int范围则走普通秒格式化,否则调用DefaultFormatter.TimeSpanHumanizeWithFractionalSeconds渲染小数。若当前文化的 formatter 不是内置的DefaultFormatter又不实现IFractionalTimeSpanFormatter,则抛出异常要求实现该接口。 - 拼接:最后用
collectionSeparator(或文化默认分隔规则)把各部分连成最终字符串。
自定义策略:一个可复制的实现骨架
基于接口契约,接入自定义策略只需两步。第一步,实现策略类型:
public class MyFractionalStrategy : IFractionalTimeSpanHumanizeStrategy { public string HumanizeWithFractionalSeconds( TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo? culture, TimeUnit maxUnit, string? collectionSeparator, int maxFractionalDigits, MidpointRounding roundingMode, bool toSymbols) { // 自行实现:校验参数、舍入、分解、格式化、拼接 return "自定义结果"; } }第二步,通过Configurator替换全局策略(Configurator.cs 的属性即可赋值):
Configurator.TimeSpanHumanizeStrategy = new MyFractionalStrategy();之后所有HumanizeWithFractionalSeconds/HumanizeToSymbolsWithFractionalSeconds调用都会命中你的实现。若自定义策略只希望覆盖部分行为,也可以继承DefaultTimeSpanHumanizeStrategy并仅覆写HumanizeWithFractionalSeconds虚方法,以复用内置的舍入与分解逻辑。
行为验证:从测试用例看接口语义边界
仓库中的 FractionalTimeSpanHumanizeTests.cs 是对该能力最直接的规格说明,也是理解九个参数组合效果的绝佳样例:
| 输入 | 参数 | 输出 | 说明 |
|---|---|---|---|
TimeSpan.FromSeconds(0.5) | precision=1, digits=7 | 0.5 seconds | 基础小数秒输出 |
TimeSpan.FromTicks(1) | digits=7 | 0.0000001 seconds | 单个 tick 完整保留(7 位精度上限) |
TimeSpan.FromTicks(12345000) | digits=3, ToEven | 1.234 seconds | 中点舍入向偶数 |
TimeSpan.FromTicks(12345000) | digits=3, AwayFromZero | 1.235 seconds | 中点舍入远离零 |
TimeSpan.FromTicks(599996000) | digits=3 | 1 minute | 先整体舍入再分解,进位到分钟 |
| 1 小时 + 0.5 秒 | precision=2, countEmptyUnits=false | 1 hour, 0.5 seconds | 小数秒可作为独立终止单位 |
| 同上 | precision=2, countEmptyUnits=true | 1 hour | 空单位占用精度名额 |
TimeSpan.FromSeconds(1.5) | fr-FR | 1,5s | 符号形式沿用文化的小数点与秒符号 |
TimeSpan.MaxValue | digits=7 | 922337203685.4775807 seconds | 极值无溢出 |
异常边界同样有据可查:maxFractionalDigits传-1或8抛出ArgumentOutOfRangeException;roundingMode传入方向性模式(MidpointRounding)2/3/4同样被拒绝(见 TimeSpanHumanizeExtensions.cs 的ValidateFractionalSecondArguments);TimeSpan.FromTicks(1)在digits=0时输出0 seconds,且在fr-FR下输出0 seconde,说明"舍入后归零"仍走本地化名词单复数规则。此外 FractionalTimeSpanLocaleSweepTests.cs 还对各语言区域做了批量扫描,确保本地化一致性。
与相邻 API 的关系
该接口与以下仓库文档形成完整的能力闭环:
- TimeSpanHumanizeExtensions.md:所有
Humanize/HumanizeWithFractionalSeconds扩展方法的入口文档; - TimeSpanDehumanizeExtensions.md:把人性化文本反向解析回
TimeSpan,可与本接口的输出形成"写—读"回路; - TimeUnit.md:
maxUnit参数的取值来源; - DefaultTimeSpanHumanizeStrategy.md:内置默认实现的完整 API 参考;
- DefaultFormatter.md:负责最终单词/符号渲染的格式化器,小数秒输出最终由其
TimeSpanHumanizeWithFractionalSeconds落地。
小结
IFractionalTimeSpanHumanizeStrategy是 Humanizer 把时间跨度人性化能力延伸到亚秒精度的关键抽象:它定义了HumanizeWithFractionalSeconds这一完整契约(九个参数分别控制单位数量、空单位计权、文化、单位上限、分隔符、小数位与舍入、符号输出),由DefaultTimeSpanHumanizeStrategy提供开箱即用的实现,并通过Configurator.TimeSpanHumanizeStrategy支持整体替换或继承覆写。无论是想要控制秒以下精度的日志与监控文案,还是需要完全自定义的时间显示策略,这个接口都是接入点。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
vnet.pytorch训练全攻略:参数调优、损失函数选择与模型评估实战
vnet.pytorch训练全攻略:参数调优、损失函数选择与模型评估实战 vnet.pytorch是一个基于PyTorch实现的V Net模型,专门用于 vol
开发工具Humanizer 中 IDateTimeOffsetHumanizeStrategy 接口详解:自定义 DateTimeOffset.Humanize 日期人性化策略
Humanizer 中 IDateTimeOffsetHumanizeStrategy 接口详解:自定义 DateTimeOffset.Humanize 日期人
开发工具深入解析 Humanizer.IDateTimeHumanizeStrategy:DateTime.Humanize 时间距离人性化策略接口
深入解析 Humanizer.IDateTimeHumanizeStrategy:DateTime.Humanize 时间距离人性化策略接口 导读 IDateT
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考