- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
导读
Humanizer.Localisation.TimeUnit是 Humanizer 库中定义时间单位的最小公共语言:它将"毫秒、秒、分钟、小时、天、周、月、年"这 8 个时间粒度固化为一个枚举,并作为桥梁连接日期人性化(DateHumanize)、时间跨度人性化(TimeSpanHumanize)、单位符号转换(ToSymbol)与多语言本地化格式化器。本文以该枚举为骨架,结合 TimeUnit.cs 源码、TimeSpanHumanizeExtensions.cs 算法与 TimeUnitToSymbolExtensions.cs 扩展方法,完整讲解其定义、底层调用链、本地化机制与实战用法。读完本文,你将掌握如何用maxUnit/minUnit精确控制人性化输出的粒度,理解月/年近似计算的边界,并能把时间单位转成任意文化的符号形式。
一、TimeUnit 枚举定义与字段速查
1.1 官方 API 定义
关联文档给出的 API 签名与字段数值如下:
public enum TimeUnit| 字段 | 数值 | 含义 |
|---|---|---|
Millisecond | 0 | 一毫秒 |
Second | 1 | 一秒 |
Minute | 2 | 一分钟 |
Hour | 3 | 一小时 |
Day | 4 | 一天 |
Week | 5 | 一周 |
Month | 6 | 一月 |
Year | 7 | 一年 |
1.2 源码中的完整实现
仓库中的实际定义位于 TimeUnit.cs,与文档完全一致,且每个成员都带有明确的 XML 文档注释。该枚举直接驻留在Humanizer.Localisation命名空间下,其设计意图从源码注释可见一斑:
Represents the time units supported by Humanizer's relative-time and duration formatters.
也就是说,TimeUnit是 Humanizer 相对时间(relative-time)与时长(duration)格式化两大功能线的公共枚举。它的取值顺序(从小到大)本身就构成了算法遍历的优先级基础——在 TimeSpanHumanizeExtensions.cs 中,库通过Enumerable.Reverse(Enum.GetValues<TimeUnit>())得到一个从大到小排列的单位数组,用于从最大单位开始逐级拆解时间跨度:
static readonly TimeUnit[] TimeUnits = [.. Enumerable.Reverse(Enum.GetValues<TimeUnit>())];这一细节说明:枚举成员的声明顺序不是随意的,它直接参与算法的单位降序遍历逻辑。
二、TimeUnit 在人性化算法中的三大核心应用
TimeUnit本身不携带行为,它的价值体现在被各大扩展方法消费的调用链中。以下是它最核心的三个应用场景。
2.1 TimeSpan.Humanize:用 maxUnit / minUnit 控制输出粒度
TimeSpan.Humanize是时长人性化的主入口,其签名(见 TimeSpanHumanizeExtensions.cs)中两个直接以TimeUnit为类型的参数决定了输出的上下边界:
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)参数语义(来自源码 XML 注释):
maxUnit(最大单位,默认TimeUnit.Week):输出中允许出现的最大时间单位。默认值为Week意味着TimeSpan.FromDays(400).Humanize()不会直接输出 "1 year",而是落到以周为最大刻度的表达。特别要注意:Month与Year一旦被选为maxUnit,它们对大跨度(超过 30 天)时间的计算是近似值——按一年 365.2425 天、一个月 30.4369 天折算(源码常量见 TimeSpanHumanizeExtensions.cs):
const double DaysInAYear = 365.2425; // 格里高利历 const double DaysInAMonth = DaysInAYear / 12;minUnit(最小单位,默认TimeUnit.Millisecond):输出中允许出现的最小时间单位。例如要忽略毫秒级噪声,可设minUnit: TimeUnit.Second。
算法在GetTimeUnitPart(TimeSpanHumanizeExtensions.cs)中按timeUnitToGet <= maximumTimeUnit && timeUnitToGet >= minimumTimeUnit过滤单位,随后用switch表达式按单位类型拆解数值(毫秒取Timespan.Milliseconds、天/周/月/年走各自的"特殊大小写"分支,见GetTimeUnitNumericalValue,TimeSpanHumanizeExtensions.cs)。
实测示例(对应 TimeSpanHumanizeTests.cs 的TimeSpanWithMaxTimeUnit测试):
TimeSpan.FromMilliseconds(2_016_000_000).Humanize(maxUnit: TimeUnit.Year); // 例如输出 "3 weeks"(当 maxUnit 为 Week 时) // 或 "1 month" 级别的近似表达(当 maxUnit 为 Month/Year 时)配合precision参数可输出多段组合,例如Humanize(precision: 4, culture: culture, maxUnit: TimeUnit.Year)(见 TimeSpanHumanizeTests.cs)。
2.2 日期人性化:DefaultHumanize 的分级阈值
在相对日期算法 DateTimeHumanizeAlgorithms.cs 中,TimeUnit是每个判断分支的返回值标签。DefaultHumanize(TimeSpan, ...)内部以一系列阈值阶梯决定输出哪个单位:
| 时间跨度条件 | 输出单位 |
|---|---|
TotalMilliseconds < 500 | TimeUnit.Millisecond(0 毫秒,即 "now") |
TotalSeconds < 60 | TimeUnit.Second |
TotalSeconds < 120 | TimeUnit.Minute(1 分钟) |
TotalMinutes < 60 | TimeUnit.Minute |
TotalMinutes < 90 | TimeUnit.Hour(1 小时) |
TotalHours < 24 | TimeUnit.Hour |
TotalHours < 48 | TimeUnit.Day |
TotalDays < 7 | TimeUnit.Day |
TotalDays < 28 | TimeUnit.Week |
TotalDays ∈ [28, 30)且同年同月 | TimeUnit.Month |
TotalDays < 345 | TimeUnit.Month(按floor(days / 29.5)折算) |
| 其余 | TimeUnit.Year(按floor(days / 365)折算) |
每次判定后,算法调用formatter.DateHumanize(TimeUnit.X, tense, count)把枚举传给格式化器,由本地化层生成具体短语(如 "a minute ago" / "in 3 days")。Tense(过去/未来)与TimeUnit一起决定了短语的最终形态。该算法同样服务于DateTime、DateTimeOffset、DateOnly、TimeOnly四种类型的人性化入口(见 DateHumanizeExtensions.cs)。
2.3 单位符号:ToSymbol 扩展方法
TimeUnitToSymbolExtensions.cs 提供了把时间单位转成符号的单行入口:
public static string ToSymbol(this TimeUnit unit, CultureInfo? culture = null) => Configurator.GetFormatter(culture).TimeUnitHumanize(unit);对应的 en-US 实测输出由 TimeUnitToSymbolTests.cs 固化:
| TimeUnit | ToSymbol()(en-US) |
|---|---|
TimeUnit.Millisecond | ms |
TimeUnit.Second | s |
TimeUnit.Minute | min |
TimeUnit.Hour | h |
TimeUnit.Day | d |
TimeUnit.Week | week |
TimeUnit.Month | mo |
TimeUnit.Year | y |
基于此,HumanizeToSymbols扩展(见 TimeSpanHumanizeExtensions.cs)可以把整个 TimeSpan 输出为紧凑符号串,例如"1h 30min",其内部正是对每个TimeUnit部件调用TimeUnitHumanize获取本地化符号后拼接(FormatTimePart中的string.Concat(amount, cultureFormatter.TimeUnitHumanize(timeUnit)),见 TimeSpanHumanizeExtensions.cs)。
三、本地化机制:TimeUnit 如何走向 100+ 语言
TimeUnit的另一个身份是本地化短语表的键。默认格式化器 DefaultFormatter.cs 通过LocalePhraseTable(由源码生成器从 YAML 生成的短语表)为每个文化解析短语,核心方法有三个:
DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit)——相对日期短语;TimeSpanHumanize(TimeUnit timeUnit, int unit, bool toWords = false)——时长短语;TimeUnitHumanize(TimeUnit timeUnit)——单位符号。
它们统一定义在 IFormatter.cs 接口中,任何自定义格式化器都必须实现。从TimeUnit到本地化文本的映射源头是各语言的 YAML 文件,例如 en.yml 中relativeDate段为每个past/future×TimeUnit组合配置了单数/复数短语:
past: minute: single: 'a minute ago' multiple: afterCount: 'ago' forms: singular: 'minute' default: 'minutes'仓库Locales/目录下包含 100 余个 yml(如 zh-CN、ja、de、ru、ar 等),这些文件经Humanizer.SourceGenerators编译期生成LocalePhraseTableCatalog,从而让TimeUnit枚举值在不同的CultureInfo下产出完全不同的文本。这正是 Humanizer "meets all your .NET needs for … dates, times, timespans" 多语言能力的底层实现路径。
对于需要语法格(grammatical case)的语言(如匈牙利语、芬兰语、马拉雅拉姆语),TimeUnit还参与IGrammaticalCaseTimeSpanFormatter接口的格变体解析(DefaultFormatter.cs),可见该枚举贯穿了从最基础的符号输出到最复杂的形态学本地化。
四、进阶用法与注意事项
4.1 以 TimeUnit 为参数的完整调用组合
Humanize的完整形态(TimeSpanHumanizeExtensions.cs)允许同时控制精度、空单位计数与文化:
timeSpan.Humanize( precision: 2, countEmptyUnits: false, culture: new CultureInfo("zh-CN"), maxUnit: TimeUnit.Year, minUnit: TimeUnit.Second, collectionSeparator: " ", toWords: false);precision:最多返回的单位数,默认 1(只返回最大单位);countEmptyUnits:是否把数值为 0 的中间单位也计入precision(前导空单位永远不计);collectionSeparator:多段输出的连接符,为 null 时使用文化默认的集合格式化器。
4.2 ToAge 与符号模式的单位边界
TimeSpan.ToAge(...)(TimeSpanHumanizeExtensions.cs)默认maxUnit = TimeUnit.Year,把时长转成 "40 years old" 式的年龄表达;HumanizeToSymbols/HumanizeToSymbolsWithFractionalSeconds同样接受TimeUnit边界参数;其中小数秒模式要求maxUnit落在TimeUnit.Second与TimeUnit.Year之间,否则抛ArgumentOutOfRangeException(ValidateFractionalSecondArguments,TimeSpanHumanizeExtensions.cs)。
4.3 需要留意的三点边界事实
- 月与年的近似性:默认
maxUnit = TimeUnit.Week时不会触发月/年近似;只有显式把maxUnit设为Month/Year,大跨度时间才会按 365.2425 天/年、30.4369 天/月的固定比率折算(源码注释明确说明这是 approximations)。 - 符号与词的差异:
toWords为 true 时单位前的数字转为文字(如 "one day"),且部分语言为词模式提供独立的短语变体(SingleWordsVariant/MultipleWordsVariant,见 DefaultFormatter.cs)。 - 枚举顺序即算法顺序:
TimeUnit的成员声明顺序被Enum.GetValues直接消费,新增单位时需谨慎调整拆解算法(GetTimeUnitNumericalValue的 switch)与其保持一致。
五、可验证的源码与测试指引
想要在本地深入验证上述行为,可直接阅读与运行以下文件:
- 枚举定义:src/Humanizer/Localisation/TimeUnit.cs
- 符号扩展与测试:src/Humanizer/TimeUnitToSymbolExtensions.cs、tests/Humanizer.Tests/TimeUnitToSymbolTests.cs
- 时长算法:src/Humanizer/TimeSpanHumanizeExtensions.cs、tests/Humanizer.Tests/TimeSpanHumanizeTests.cs
- 日期算法:src/Humanizer/DateTimeHumanizeStrategy/DateTimeHumanizeAlgorithms.cs、tests/Humanizer.Tests/DateHumanize.cs
- 本地化短语源:src/Humanizer/Locales/en.yml(及同目录其他语言的 yml)
在测试项目中直接运行dotnet test tests/Humanizer.Tests/Humanizer.Tests.csproj --filter "FullyQualifiedName~TimeUnitToSymbolTests"即可复现文中符号映射表,--filter "FullyQualifiedName~TimeSpanWithMaxTimeUnit"可验证maxUnit边界行为。理解TimeUnit这一最小枚举,就等于拿到了解读 Humanizer 全部时间人性化能力的钥匙。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 的 TimeUnit 枚举:支撑相对时间、时长与速率本地化格式化的时间单位体系
Humanizer 的 TimeUnit 枚举:支撑相对时间、时长与速率本地化格式化的时间单位体系 导读 TimeUnit 是 Humanizer 库中定义时间
开发工具Humanizer 时间单位体系(TimeUnit)详解:枚举定义、符号映射与本地化机制
Humanizer 时间单位体系(TimeUnit)详解:枚举定义、符号映射与本地化机制 TimeUnit 是 Humanizer 中用于描述相对时间与时间跨度
开发工具Humanizer 的 Tense 枚举:深入解析过去与未来时态在 .NET 时间人性化中的定位与用法
Humanizer 的 Tense 枚举:深入解析过去与未来时态在 .NET 时间人性化中的定位与用法 导读 Tense 是 Humanizer 本地化(Loc
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考