Humanizer 中 IDateTimeOffsetHumanizeStrategy 接口详解:定制 DateTimeOffset.Humanize 的“时间距离转文字“策略 开发工具【免费下载链接】HumanizerHumanizer 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 的IDateTimeOffsetHumanizeStrategy接口展开讲清楚它承担什么职责、Humanize(DateTimeOffset, DateTimeOffset, CultureInfo)方法的契约细节以及两个内置策略DefaultDateTimeOffsetHumanizeStrategy与PrecisionDateTimeOffsetHumanizeStrategy的算法差异。读完本文你可以掌握如何阅读该接口的实现与调用链、如何通过Configurator.DateTimeOffsetHumanizeStrategy注册自定义策略并能写出带本地化输出、经过测试验证的自定义时间距离算法。一、接口定位与定义IDateTimeOffsetHumanizeStrategy是 Humanizer 为DateTimeOffset.Humanize提供的策略扩展点。接口 XML 文档说明得很直接Implement this interface to create a new strategy for DateTime.Humanize and hook it in the Configurator.DateTimeOffsetHumanizeStrategy实现此接口以创建新的策略并把它挂接到Configurator.DateTimeOffsetHumanizeStrategy上。接口定义见 IDateTimeOffsetHumanizeStrategy.csnamespace Humanizer; /// summary /// Implement this interface to create a new strategy for DateTime.Humanize /// and hook it in the Configurator.DateTimeOffsetHumanizeStrategy /// /summary public interface IDateTimeOffsetHumanizeStrategy { /// summary /// Calculates the distance of time in words between two provided dates /// used for DateTimeOffset.Humanize /// /summary string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo? culture); }版本 2.13.14 的 API 文档快照中给出的签名见 IDateTimeOffsetHumanizeStrategy API 文档与之完全一致当前仓库源码中的CultureInfo参数带有可空标注CultureInfo?表示传null时回退到当前线程文化。Humanize 方法参数契约文档对三个参数的说明如下结合源码可以进一步明确其语义参数类型语义inputSystem.DateTimeOffset要 humanize 的目标时间点带时区偏移comparisonBaseSystem.DateTimeOffset比较基准时间点通常由扩展方法传入缺省为DateTimeOffset.UtcNowcultureSystem.Globalization.CultureInfo用于本地化输出的文化为null时使用当前线程文化返回值为string即时间距离的文字描述distance of time in words例如an hour from now、30 minutes ago。该接口只有两个直接实现类文档 Derived 一节列出源码可以印证DefaultDateTimeOffsetHumanizeStrategyPrecisionDateTimeOffsetHumanizeStrategy二、调用链从扩展方法到策略理解这个接口首先要看它在哪里被消费。入口是 DateHumanizeExtensions.cs 中的扩展方法public static string Humanize(this DateTimeOffset input, DateTimeOffset? dateToCompareAgainst null, CultureInfo? culture null) { var comparisonBase dateToCompareAgainst ?? DateTimeOffset.UtcNow; return Configurator.DateTimeOffsetHumanizeStrategy.Humanize(input, comparisonBase, culture); }调用链非常短三层结构扩展方法层DateTimeOffset.Humanize(...)负责补齐默认值——没有传dateToCompareAgainst时以DateTimeOffset.UtcNow作为基准配置层Configurator.csConfigurator.DateTimeOffsetHumanizeStrategy属性持有当前生效的策略默认值是new DefaultDateTimeOffsetHumanizeStrategy()策略层你实现的IDateTimeOffsetHumanizeStrategy.Humanize。Configurator中该属性的 XML 文档Configurator API 文档 对应条目还给出了重要的使用约束This property should be set only once during application startup before any humanization operations occur. For thread-safety, use volatile reads or appropriate synchronization when accessing this property in multi-threaded scenarios. In production applications, avoid changing this value after the application has started serving requests.也就是说该属性应在应用启动阶段例如Program.Main或ModuleInitializer一次性设置不要在生产运行时动态切换。这与测试代码的写法一致——DateTimeOffsetHumanizeTests.cs 中每个用例都在断言前重新赋值为new DefaultDateTimeOffsetHumanizeStrategy()或new PrecisionDateTimeOffsetHumanizeStrategy(0.75)以隔离策略间的影响。此外还有一个可空输入的重载DateTimeOffset?为null时不走策略直接返回本地化的never见 DateHumanizeExtensions.cs 与测试用例Never这一点在实现自定义策略时无需处理由扩展方法层负责。三、两个内置策略及其算法差异DefaultDateTimeOffsetHumanizeStrategy先转 UTC再按距离感分档DefaultDateTimeOffsetHumanizeStrategy.cs 的全部实现只有一行public string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo? culture) DateTimeHumanizeAlgorithms.DefaultHumanize(input.UtcDateTime, comparisonBase.UtcDateTime, culture);关键细节是input.UtcDateTime/comparisonBase.UtcDateTime的转换两个带不同时区偏移的DateTimeOffset必须统一换算成 UTC 时刻再比较否则墙钟时间相同但实际时刻不同的两个点会被误判。这正是测试用例DefaultStrategy_DifferentOffsetsDateTimeOffsetHumanizeTests.cs覆盖的场景var inputTime new DateTimeOffset(2015, 07, 05, 03, 0, 0, new(2, 0, 0)); // 02:00 var baseTime new DateTimeOffset(2015, 07, 05, 02, 30, 0, new(1, 0, 0)); // 01:00 // UTC 时刻分别为 01:00 与 01:30实际相差 30 分钟 Assert.Equal(30 minutes ago, inputTime.Humanize(baseTime));换算后的差值交给 DateTimeHumanizeAlgorithms.DefaultHumanize它采用的是一套经典的距离感分档规则源自时间距离计算的经典做法时间差输出单位示例输出 500 ms毫秒档now 60 s秒 120 s1 分钟 60 min分钟 90 min1 小时 24 h小时 48 h天 7 天天 28 天周28 ~ 30 天若同一月则 1 个月否则按天 345 天月按 29.5 天/月折算≥ 345 天年按 365 天/年折算最小 1 年其中sameMonth的判断比较基准 ±1 个月后是否等于输入日期专门服务于 28~30 天这个跨月模糊区var sameMonth comparisonBase.Date.AddMonths(tense Tense.Future ? 1 : -1) input.Date;PrecisionDateTimeOffsetHumanizeStrategy可调精度的向上取整PrecisionDateTimeOffsetHumanizeStrategy.cs 使用主构造函数的默认参数public class PrecisionDateTimeOffsetHumanizeStrategy(double precision .75) : IDateTimeOffsetHumanizeStrategy { readonly double precision precision; public string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo? culture) DateTimeHumanizeAlgorithms.PrecisionHumanize(input.UtcDateTime, comparisonBase.UtcDateTime, precision, culture); }precision表示逼近精度默认0.75。同样先做UtcDateTime换算再交给 DateTimeHumanizeAlgorithms.PrecisionHumanize 与内部重载L48-L131。它的核心思路是从小单位向大单位逐级进位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 0.75时毫秒达到999 × 0.75 ≈ 749就进位到秒秒达到59 × 0.75 ≈ 44就进位到分钟。月份和年份则是按30天/月、365天/年的因子折算并配合同样的 precision 判定是否向上取整// month calculation if (days 31 days 365 * precision) { var factor Convert.ToInt32(Math.Floor((double)days / 30)); months days 30 * (factor precision) ? factor 1 : factor; }得到某个单位后从大到小依次判断years months days hours minutes seconds命中即返回因此最终总是单一单位的短语。两个策略的差异可以概括为Default固定阈值的人类直觉分档如 120 s 内说1 分钟、28~30 天看是否跨月Precision用单一 precision 因子驱动所有进位行为可预测、可调参适合需要近似值而非口语化表达的场景。结果如何本地化两个策略最终都不直接拼字符串而是通过Configurator.GetFormatter(culture)取出当前文化的IFormatter调用其DateHumanize(TimeUnit, Tense, int)产出本地化短语var formatter Configurator.GetFormatter(culture); return formatter.DateHumanize(TimeUnit.Year, tense, years);IFormatter.DateHumanize的契约见 IFormatter.cstimeUnit是时间单位、timeUnitTense是过去/未来时态、unit是数量。这解释了为什么测试Humanize_UsesSpecifiedCulture能在 100 多个 locale 上验证同一策略策略只决定哪个单位、几个单位、什么时态文字本身完全由文化对应的 formatter 决定DateTimeOffsetHumanizeTests.cs[MemberData(nameof(LocaleCoverageData.FormatterExpectationTheoryData), MemberType typeof(LocaleCoverageData))] public void Humanize_UsesSpecifiedCulture(string localeName, string expectedYesterday, string _) { var baseTime new DateTimeOffset(2024, 1, 2, 12, 0, 0, TimeSpan.Zero); var culture new CultureInfo(localeName); Assert.Equal(expectedYesterday, baseTime.AddDays(-1).Humanize(baseTime, culture)); }因此culture参数为null时回退当前线程文化非 null 时如new CultureInfo(fr-FR)会解析出对应文化的 formatter输出该语言的相对时间短语。四、注册与自定义策略实战注册内置策略切换策略只需在启动阶段给Configurator赋值官方文档给出的hook it in方式即using Humanizer; // 使用默认策略这本来就是默认值通常无需显式写 Configurator.DateTimeOffsetHumanizeStrategy new DefaultDateTimeOffsetHumanizeStrategy(); // 使用 precision 0.75 的精度策略 Configurator.DateTimeOffsetHumanizeStrategy new PrecisionDateTimeOffsetHumanizeStrategy(0.75);注意PrecisionDateTimeOffsetHumanizeStrategy的构造参数是可选的new PrecisionDateTimeOffsetHumanizeStrategy()等价于 precision 0.75。测试中常见的基准 输入组合可以直接调用扩展方法也可以绕过扩展方法直接调用策略DateTimeOffsetHumanizeTests.csvar strategy new PrecisionDateTimeOffsetHumanizeStrategy(0.75); var inputTime new DateTimeOffset(2019, 01, 29, 0, 0, 0, TimeSpan.Zero); var baseTime new DateTimeOffset(2019, 03, 29, 0, 0, 0, TimeSpan.Zero); Assert.Equal(2 months ago, strategy.Humanize(inputTime, baseTime, null));实现一个自定义策略自定义策略就是实现IDateTimeOffsetHumanizeStrategy的单个方法。下面是一个可直接编译的示例在默认策略基础上把 48 小时以内统一显示为1 天以内并把昨天/明天这类表达交给默认策略using System.Globalization; using Humanizer; /// summary /// 自定义策略1~2 天之间的时间差统一按1 天输出其余沿用默认策略。 /// /summary public sealed class CoarseDayStrategy : IDateTimeOffsetHumanizeStrategy { private readonly IDateTimeOffsetHumanizeStrategy _fallback new DefaultDateTimeOffsetHumanizeStrategy(); public string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo? culture) { var diff input.UtcDateTime - comparisonBase.UtcDateTime; if (Math.Abs(diff.TotalHours) is 24 and 48) { // 复用 formatter 保证本地化输出过去式 - yesterday将来式 - tomorrow var formatter Configurator.GetFormatter(culture); var tense diff TimeSpan.Zero ? Tense.Past : Tense.Future; return formatter.DateHumanize(TimeUnit.Day, tense, 1); } return _fallback.Humanize(input, comparisonBase, culture); } }实现要点都来自源码证据而非约定自行处理时区换算。扩展方法不会替你转 UTC——它只是把input、comparisonBase原样传进来。内置两个策略都在算法入口做UtcDateTime换算自定义策略如果比较两个不同偏移的DateTimeOffset也应先统一为 UTC本地化文字走 formatter。策略层只应产出单位 数量 时态通过Configurator.GetFormatter(culture).DateHumanize(...)拿本地化短语注意Configurator的部分成员是internalGetFormatter是内部方法外部项目若需要格式化文本更稳妥的做法是复用内置策略或委托其输出上例中直接调用DateHumanize的前提是你所在程序集能访问到该 API否则可改为委托给内置策略输出再按规则改写时态判定用input与comparisonBase的大小关系与 DateTimeHumanizeAlgorithms.cs 中var tense input comparisonBase ? Tense.Future : Tense.Past;保持一致启动时注册一次Configurator.DateTimeOffsetHumanizeStrategy new CoarseDayStrategy();遵循 Configurator.cs 中启动阶段设置、避免运行中更换的备注。验证自定义策略时可以照搬仓库测试的写法固定comparisonBase、断言确定字符串并覆盖相同偏移 / 不同偏移两类输入参考 DateTimeOffsetHumanizeTests.cs 中的DefaultStrategy_SameOffset、DefaultStrategy_DifferentOffsets、DefaultStrategy_WeekAcrossDifferentOffsets这样既能验证算法分档也能验证时区换算正确性。五、测试验证与预期输出DateTimeOffsetHumanizeTests.cs 为该接口及其两个实现提供了完整的行为基线测试类标注[UseCulture(en-US)]以下输出均为英文文化用例策略输入 / 基准预期输出DefaultStrategy_SameOffsetDefault04:00Z / 03:00Zan hour from nowDefaultStrategy_DifferentOffsetsDefault03:0002:00 / 02:3001:0030 minutes agoDefaultStrategy_WeekAcrossDifferentOffsetsDefault2024-01-08 03:00-05:00 / 2024-01-01 10:0002:00one week from nowPrecisionStrategy_SameOffsetPrecision(0.75)07-05 04:00Z / 07-04 05:00ZtomorrowPrecisionStrategy_DifferentOffsetsPrecision(0.75)03:4502:00 / 02:30-05:006 hours agoPrecisionStrategy_TwoMonthsAroundSixtyDaysPrecision(0.75)1/27、1/28、1/29 / 3/292 months agoNever—null输入never其中PrecisionStrategy_SameOffset值得注意相差 23 小时因为23 23 × 0.75 ≈ 17.25触发天数进位输出是tomorrow而非 23 hours from now——这直接体现了 precision 进位规则的实际效果。六、相关文档与延伸阅读本文的骨架来自版本 2.13.14 的 API 文档快照 IDateTimeOffsetHumanizeStrategy.md其中列出的派生类文档与当前仓库实现一一对应当前仓库源码中接口签名与文档一致culture参数带可空标注DefaultDateTimeOffsetHumanizeStrategy 文档 / 源码PrecisionDateTimeOffsetHumanizeStrategy 文档 / 源码Configurator 文档 / 源码DateHumanizeExtensions 源码DateTimeOffset.Humanize扩展方法入口DateTimeHumanizeAlgorithms 源码Default 与 Precision 两套算法的完整实现IFormatter 源码策略最终依赖的本地化格式化契约DateTimeOffsetHumanizeTests 测试行为基线与多语言验证如果你还需要DateTime、DateOnly、TimeOnly的时间距离 humanizeConfigurator上另有DateTimeHumanizeStrategy、DateOnlyHumanizeStrategyNET 6、TimeOnlyHumanizeStrategyNET 6三个同类策略属性模式与本文的DateTimeOffsetHumanizeStrategy完全一致。七、小结IDateTimeOffsetHumanizeStrategy用一个方法定义了整个DateTimeOffset.Humanize的可替换行为Humanize(input, comparisonBase, culture)把两个时间点 一种文化变成一句本地化的时间距离短语。仓库中可以看到完整闭环——扩展方法补默认值、Configurator持有策略、DateTimeHumanizeAlgorithms提供 Default固定分档与 Precision可调进位默认 0.75两套算法、IFormatter.DateHumanize负责本地化文字、DateTimeOffsetHumanizeTests固定了跨时区与多语言的行为基线。基于这些证据自定义策略时最关键的两件事是自己处理UtcDateTime换算以及把文字产出交给 formatter 以保证本地化。赞分享开发工具【免费下载链接】HumanizerHumanizer 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 中 IDateTimeOffsetHumanizeStrategy 接口详解自定义 DateTimeOffset.Humanize 日期人性化策略Humanizer 中 IDateTimeOffsetHumanizeStrategy 接口详解自定义 DateTimeOffset.Humanize 日期人开发工具Humanizer IDateTimeHumanizeStrategy 接口定制 DateTime.Humanize 的相对时间转换策略Humanizer IDateTimeHumanizeStrategy 接口定制 DateTime.Humanize 的相对时间转换策略 Humanizer开发工具Humanizer 中 IDateOnlyHumanizeStrategy 接口全解析自定义 DateOnly.Humanize 的人性化时间策略Humanizer 中 IDateOnlyHumanizeStrategy 接口全解析自定义 DateOnly.Humanize 的人性化时间策略 本篇指南围开发工具上一篇3分钟将Windows电脑变身为专业WiFi热点VirtualRouter使用指南下一篇免费WiFi热点终极指南3分钟将Windows电脑变身为专业路由器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考