news 2026/9/26 10:21:04

Humanizer PrecisionTimeOnlyHumanizeStrategy 深度解析:从 precision 参数到时间差近似算法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Humanizer PrecisionTimeOnlyHumanizeStrategy 深度解析:从 precision 参数到时间差近似算法
  • 开发工具

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

导读:本文围绕 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);

三个参数的职责:

参数类型含义
inputTimeOnly要被"人化"的目标时刻
comparisonBaseTimeOnly比较基准时刻,用于计算距离
cultureCultureInfo?输出语言与文化格式;传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 时
毫秒 ≥ 阈值 → 秒 +1999 * precision749.25 ms499.5 ms
秒 ≥ 阈值 → 分 +159 * precision44.25 s29.5 s
分 ≥ 阈值 → 时 +159 * precision44.25 min29.5 min
时 ≥ 阈值 → 天 +123 * precision17.25 h11.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提供了两套内置策略:

维度DefaultTimeOnlyHumanizeStrategyPrecisionTimeOnlyHumanizeStrategy
实现位置DefaultTimeOnlyHumanizeStrategy.csPrecisionTimeOnlyHumanizeStrategy.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"四个维度的组合行为,可作为你集成时的手工验证样例。

使用限制与注意事项

  1. 平台限制:PrecisionTimeOnlyHumanizeStrategy仅在NET6_0_OR_GREATER下编译可用;面向 .NET Framework / .NET Standard 旧目标的程序无法使用TimeOnly相关 API;
  2. precision 取值范围:从算法阈值999*precision、59*precision等公式可以推断,precision设计为 (0, 1] 区间内的比例值;0.75 是官方默认,1 表示"凑满整单位才进位",趋近 0 表示"几乎立即进位到最大单位";
  3. TimeOnly 的语义边界:TimeOnly无日期分量,月/年进位分支在纯时间场景不会触发,若需跨天甚至跨年的距离人化,请使用DateTime/DateOnly的对应策略(如PrecisionDateTimeHumanizeStrategy);
  4. 全局替换的影响范围:更换Configurator.TimeOnlyHumanizeStrategy会影响应用中所有TimeOnly.Humanize调用点,务必在启动期完成替换,避免运行期竞态;
  5. 文档与源码的差异: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

项目地址:https://gitcode.com/gh_mirrors/hu/Humanizer
点击查看免费下载
上一篇:高效文档下载自动化:kill-doc浏览器脚本让免费文档下载如此简单
下一篇:htop 的 NetBSD 支持实现:基于 kvm(3)/sysctl(3) 的进程采集与 curses 库选择机制

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

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

MCU选型不是参数比拼,而是BOM驱动的系统工程

1. 选型不是填空题&#xff0c;是系统工程&#xff1a;从BOM配单反推MCU真实需求我干硬件十年&#xff0c;经手过三百多个量产项目&#xff0c;最常被问的问题不是“哪个MCU性能最强”&#xff0c;而是“为什么我们用GD32替换了STM32后&#xff0c;产线良率掉了2%&#xff1f;”…

作者头像 李华
网站建设 2026/9/26 10:20:18

Windows 11右键菜单恢复Win10经典样式全方案

1. 为什么 Windows 11 的右键菜单让人“手慢半拍”&#xff1f;这不是审美问题&#xff0c;是交互逻辑的断层刚升级到 Windows 11 的那几天&#xff0c;我连新建一个文本文档都要多点一次——不是找不到&#xff0c;是得先点开“显示更多选项”&#xff0c;再在二级菜单里找“新…

作者头像 李华