- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
导读:本文围绕 Humanizer 2.14.1 API 文档中的
PrecisionTimeOnlyHumanizeStrategy类展开,深入剖析这套面向 .NET 6+TimeOnly类型的"精度可调"时间差自然语言化方案。读完后你将掌握precision默认值 0.75 的真实语义、Humanize方法的调用契约与参数含义、底层近似进位算法的完整实现逻辑,并能通过Configurator.TimeOnlyHumanizeStrategy将该策略接入自己的应用,同时理解它与默认策略的取舍关系。
类概览:一个面向 TimeOnly 的精度型时间差计算器
PrecisionTimeOnlyHumanizeStrategy是 Humanizer 中负责把两个时刻之间的距离转换成人类可读句子的计算器之一,其特点是"基于精度(precision)的近似计算"。API 参考文档给出的类定义为:
public class PrecisionTimeOnlyHumanizeStrategy : Humanizer.DateTimeHumanizeStrategy.ITimeOnlyHumanizeStrategy对应仓库源码中的实际声明位于 PrecisionTimeOnlyHumanizeStrategy.cs,可以看到:
- 继承关系:直接继承自
System.Object,没有中间基类; - 接口实现:实现
ITimeOnlyHumanizeStrategy接口; - 平台约束:整个类型被
#if NET6_0_OR_GREATER条件编译包裹,仅在 .NET 6.0 及更高版本可用——这是因为TimeOnly是 .NET 6 引入的"仅表示一天内时间"的结构体类型。
接口定义位于 ITimeOnlyHumanizeStrategy.cs,契约非常精简:
public interface ITimeOnlyHumanizeStrategy { string Humanize(TimeOnly input, TimeOnly comparisonBase, CultureInfo? culture); }实现该接口即可创建自己的TimeOnly.Humanize策略,并通过Configurator.TimeOnlyHumanizeStrategy挂载到全局配置中。
构造函数与 precision 参数:默认 0.75 的语义
API 文档记载,构造函数签名为:
public PrecisionTimeOnlyHumanizeStrategy(double precision=0.75);源码中采用的是 C# 主构造函数(primary constructor)语法:
public class PrecisionTimeOnlyHumanizeStrategy(double precision = .75) : ITimeOnlyHumanizeStrategy { readonly double precision = precision; ... }要点如下:
| 项目 | 说明 |
|---|---|
参数precision | 近似的精度(approximation 精度),类型为System.Double |
| 默认值 | 不传参时使用0.75 |
| 内部存储 | 通过readonly double precision = precision;在构造时冻结,之后不可变更 |
precision的含义可以理解为:当距离"攒够"某个时间单位的多大比例时,就向上进位到下一个更大的单位。比例阈值越高,输出越倾向于保留更精细的小单位描述;比例阈值越低,越容易提前进位,输出就越"粗略"。默认值0.75是一种折中:大约"过了四分之三就进位"。具体的进位规则见下文"底层算法"一节。
Humanizer 还提供了一组同源的精度型策略,例如面向DateTime的 PrecisionDateTimeHumanizeStrategy.cs,其构造函数签名、默认精度与内部实现(委托给DateTimeHumanizeAlgorithms.PrecisionHumanize)完全一致,只是入参类型不同。
Humanize 方法与参数说明
API 文档记载的方法签名为:
public string Humanize(System.TimeOnly input, System.TimeOnly comparisonBase, System.Globalization.CultureInfo culture);源码中的实际签名将culture声明为可空类型,且方法体只有一行——直接委托给共享算法:
public string Humanize(TimeOnly input, TimeOnly comparisonBase, CultureInfo? culture) => DateTimeHumanizeAlgorithms.PrecisionHumanize(input, comparisonBase, precision, culture);三个参数的职责:
| 参数 | 类型 | 含义 |
|---|---|---|
input | TimeOnly | 要被"人化"的目标时刻 |
comparisonBase | TimeOnly | 比较基准时刻,用于计算距离 |
culture | CultureInfo? | 输出语言与文化格式;传null时使用当前线程文化 |
返回值:System.String,即"本地化且人化的两时刻距离描述"。例如在 en-US 文化下输出"5 hours from now",在法语文化下输出"demain"(明天)。文档中明确描述该方法"Returns localized & humanized distance of time between two dates; given a specific precision",即输出同时受precision和culture两个维度影响。
时态判定
距离描述带有"将来/过去"语义。在算法内部(DateTimeHumanizeAlgorithms.cs):
public static string PrecisionHumanize(TimeOnly input, TimeOnly comparisonBase, double precision, CultureInfo? culture) { var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); var tense = input > comparisonBase ? Tense.Future : Tense.Past; return PrecisionHumanize(ts, tense, precision, culture); }- 取两个时刻
Ticks之差的绝对值构造TimeSpan(距离本身不带方向); input > comparisonBase时tense = Future(输出形如 "from now"),否则tense = Past(输出形如 "ago");TimeOnly只包含一天内的时间分量,因此理论最大距离不超过 24 小时。
底层算法:PrecisionHumanize 的近似与进位逻辑
这是本文最核心的部分。DateTimeHumanizeAlgorithms中的私有重载(DateTimeHumanizeAlgorithms.cs)完整实现了"从小单位向大单位逐级近似"的流程。
第一步:逐级向上取整
int seconds = ts.Seconds, minutes = ts.Minutes, hours = ts.Hours, days = ts.Days; int years = 0, months = 0; // start approximate from smaller units towards bigger ones if (ts.Milliseconds >= 999 * precision) { seconds += 1; } if (seconds >= 59 * precision) { minutes += 1; } if (minutes >= 59 * precision) { hours += 1; } if (hours >= 23 * precision) { days += 1; }这里可以清晰看到precision对进位阈值的作用:
| 进位判定 | 阈值公式 | precision=0.75 时 | precision=0.5 时 |
|---|---|---|---|
| 毫秒 ≥ 阈值 → 秒 +1 | 999 * precision | 749.25 ms | 499.5 ms |
| 秒 ≥ 阈值 → 分 +1 | 59 * precision | 44.25 s | 29.5 s |
| 分 ≥ 阈值 → 时 +1 | 59 * precision | 44.25 min | 29.5 min |
| 时 ≥ 阈值 → 天 +1 | 23 * precision | 17.25 h | 11.5 h |
可见precision 越小,进位越"慷慨",结果越粗粒度。测试 TimeOnlyHumanizeTests.cs 中,用new PrecisionTimeOnlyHumanizeStrategy(0.75)计算 18:10:49 与 13:07:04 的距离(约 5 小时 3 分 45 秒),因为 45 秒 ≥ 44.25 秒而进位,最终输出"5 hours from now"。
第二步:月与年的近似
// month calculation if (days >= 30 * precision & days <= 31) { months = 1; } if (days > 31 && days < 365 * precision) { var factor = Convert.ToInt32(Math.Floor((double)days / 30)); months = days >= 30 * (factor + precision) ? factor + 1 : factor; } // year calculation if (days >= 365 * precision && days <= 366) { years = 1; } if (days > 365) { var factor = Convert.ToInt32(Math.Floor((double)days / 365)); years = days >= 365 * (factor + precision) ? factor + 1 : factor; }- 月按 30 天估算,年按 365 天估算,采用"基础因子 + precision 余量"决定是否再加一档;
- 注意:
TimeOnly距离最多 24 小时,月/年分支在TimeOnly场景下永远不会命中,这些分支主要服务于同算法的DateTime/DateOnly版本——从源码结构看,这是算法被多类型复用的证据。
第三步:从大到小选取最大非零单位输出
var formatter = Configurator.GetFormatter(culture); if (years > 0) return formatter.DateHumanize(TimeUnit.Year, tense, years); if (months > 0) return formatter.DateHumanize(TimeUnit.Month, tense, months); if (days > 0) return formatter.DateHumanize(TimeUnit.Day, tense, days); if (hours > 0) return formatter.DateHumanize(TimeUnit.Hour, tense, hours); if (minutes > 0) return formatter.DateHumanize(TimeUnit.Minute, tense, minutes); if (seconds > 0) return formatter.DateHumanize(TimeUnit.Second, tense, seconds); return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0);- 输出采用"只讲最大单位"策略:只要某个大单位非零,就不再提更小的单位;
- 全部为零时输出
TimeUnit.Millisecond的零值描述(各语言的 "now/现在" 通常来自这里); - 文案的实际生成依赖
Configurator.GetFormatter(culture)解析出的语言格式化器,因此同一距离在不同文化下会得到完全不同的表达。
与默认策略的对比:什么时候选 Precision
Humanizer 为TimeOnly提供了两套内置策略:
| 维度 | DefaultTimeOnlyHumanizeStrategy | PrecisionTimeOnlyHumanizeStrategy |
|---|---|---|
| 实现位置 | DefaultTimeOnlyHumanizeStrategy.cs | PrecisionTimeOnlyHumanizeStrategy.cs |
| 算法 | DefaultHumanize:基于一系列固定阈值分档(如 <120s → "1 minute"、<90min → "1 hour"、<48h → "1 day") | PrecisionHumanize:基于precision比例的逐级进位 |
| 可调参数 | 无 | precision(默认 0.75) |
| 典型输出(12 小时差) | "12 hours from now" | precision=0.5 时进位为"demain"(1 天后) |
默认策略的完整分档逻辑见 DateTimeHumanizeAlgorithms.cs,它内置了sameMonth、days等针对日期场景的额外信息,而在TimeOnly重载中固定传入sameMonth: true, days: 0(因为纯时间没有日期分量)。
选型建议:
- 追求"所见即所得"的精确时间差(如"4 hours ago")→ 默认策略或高 precision 值;
- 希望输出更"大而化之"、贴近口语习惯(如 11.5 小时就说"明天")→ 调低 precision 值;
- 需要跨策略统一的行为边界、可复现的近似规则 → 使用 Precision 系列并显式指定
precision。
接入与配置:替换 Configurator.TimeOnlyHumanizeStrategy
TimeOnly.Humanize的入口在 DateHumanizeExtensions.cs:
public static string Humanize(this TimeOnly input, TimeOnly? timeToCompareAgainst = null, bool useUtc = true, CultureInfo? culture = null) { var comparisonBase = timeToCompareAgainst ?? TimeOnly.FromDateTime(useUtc ? DateTime.UtcNow : DateTime.Now); return Configurator.TimeOnlyHumanizeStrategy.Humanize(input, comparisonBase, culture); }- 不传
timeToCompareAgainst时,以当前时刻为基准(useUtc默认true取 UTC); - 最终委托给全局属性
Configurator.TimeOnlyHumanizeStrategy,其默认值是DefaultTimeOnlyHumanizeStrategy(见 Configurator.cs)。
要在应用中使用精度策略,只需在启动阶段替换该全局属性:
using Humanizer; using Humanizer.Configuration; using Humanizer.DateTimeHumanizeStrategy; // 替换全局 TimeOnly 人化策略,precision 按需调整 Configurator.TimeOnlyHumanizeStrategy = new PrecisionTimeOnlyHumanizeStrategy(0.75); // 之后所有 TimeOnly.Humanize 调用都会走精度算法 var distance = new TimeOnly(18, 10, 49).Humanize(new TimeOnly(13, 07, 04)); // en-US 下输出: "5 hours from now" // 也可以绕过全局配置,直接用策略实例计算 var result = new PrecisionTimeOnlyHumanizeStrategy(0.5) .Humanize(new TimeOnly(13, 08, 05), new TimeOnly(1, 08, 05), CultureInfo.GetCultureInfo("fr")); // 法语下输出: "demain"(12 小时差在 0.5 精度下进位为 1 天)注意事项:
- 配置时机:
Configurator中所有策略属性都应"在应用启动阶段、任何 humanization 操作发生之前"设置一次;生产环境不要在请求处理过程中动态修改(Configurator.cs 的注释明确了这一线程安全约定); - 可空重载:
TimeOnly?为null时Humanize返回格式化器的"never"文案(DateHumanizeExtensions.cs),这在可选时间字段展示场景很常用; - 文化参数:
culture传null时使用当前线程文化;测试 TimeOnlyHumanizeTests.cs 验证了显式指定文化时输出与对应DateHumanize格式化器一致。
测试与行为验证
仓库测试 TimeOnlyHumanizeTests.cs 提供了可直接对照的行为基准:
PrecisionStrategy_NextDay(L90-L101):new PrecisionTimeOnlyHumanizeStrategy(0.75)计算 18:10:49 与 13:07:04 →"5 hours from now"(验证 45 秒在 0.75 精度下进位为 1 分钟);StrategiesAreIsolatedAcrossParallelCultures(L33-L76):new PrecisionTimeOnlyHumanizeStrategy(0.5)在法语文化下计算 12 小时差 →"demain",验证 12h ≥ 23×0.5=11.5h 进位为 1 天,同时验证不同策略在并行多文化场景下彼此隔离;DefaultStrategy_SameTime(L9-L18):相同时刻 →"now";DefaultStrategy_HoursAgo(L79-L88):13:07:02 相对 17:07:05 →"4 hours ago"(过去时态)。
这些用例同时印证了"距离 + 时态 + 文化 + precision"四个维度的组合行为,可作为你集成时的手工验证样例。
使用限制与注意事项
- 平台限制:
PrecisionTimeOnlyHumanizeStrategy仅在NET6_0_OR_GREATER下编译可用;面向 .NET Framework / .NET Standard 旧目标的程序无法使用TimeOnly相关 API; - precision 取值范围:从算法阈值
999*precision、59*precision等公式可以推断,precision设计为 (0, 1] 区间内的比例值;0.75 是官方默认,1 表示"凑满整单位才进位",趋近 0 表示"几乎立即进位到最大单位"; - TimeOnly 的语义边界:
TimeOnly无日期分量,月/年进位分支在纯时间场景不会触发,若需跨天甚至跨年的距离人化,请使用DateTime/DateOnly的对应策略(如PrecisionDateTimeHumanizeStrategy); - 全局替换的影响范围:更换
Configurator.TimeOnlyHumanizeStrategy会影响应用中所有TimeOnly.Humanize调用点,务必在启动期完成替换,避免运行期竞态; - 文档与源码的差异:API 参考文档中
culture参数标注为非空CultureInfo,实际源码签名是可空CultureInfo?,传null时回退到当前线程文化——以源码行为为准。
至此,你已完整掌握PrecisionTimeOnlyHumanizeStrategy的类结构、precision参数语义、Humanize调用契约、底层近似算法与接入方式,可以按需在项目中定制"精度可控、文化自适应"的时间差展示逻辑。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 日期时间人性化策略解析:从 Default 到 Precision 的可配置相对时间算法
Humanizer 日期时间人性化策略解析:从 Default 到 Precision 的可配置相对时间算法 本文系统讲解 .NET 库 Humanizer 中
开发工具Humanizer PrecisionTimeOnlyHumanizeStrategy 详解:用精度参数控制 TimeOnly 时间距离的人性化输出
Humanizer PrecisionTimeOnlyHumanizeStrategy 详解:用精度参数控制 TimeOnly 时间距离的人性化输出 本指南围绕
开发工具Humanizer 中 PrecisionTimeOnlyHumanizeStrategy 精度式时间人性化策略详解
Humanizer 中 PrecisionTimeOnlyHumanizeStrategy 精度式时间人性化策略详解 在 .NET 应用里把 TimeOnly
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考