- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
导读
IDateTimeOffsetHumanizeStrategy是 Humanizer 库中负责将DateTimeOffset时间差转换为人类可读短语(如 "an hour from now"、"30 minutes ago")的策略抽象接口。通过实现该接口并挂载到Configurator.DateTimeOffsetHumanizeStrategy,你可以完全掌控DateTimeOffset.Humanize()的措辞与精度。读完本文,你将理解该接口的契约、两个内置策略(默认策略与精度策略)的底层算法差异,并能写出自己的自定义策略并接入全局配置。
接口契约:一个方法,三处参数
接口定义位于 src/Humanizer/DateTimeHumanizeStrategy/IDateTimeOffsetHumanizeStrategy.cs,全文如下:
namespace 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); }方法签名
string Humanize(System.DateTimeOffset input, System.DateTimeOffset comparisonBase, System.Globalization.CultureInfo? culture);三个参数的含义如下:
| 参数 | 类型 | 说明 |
|---|---|---|
input | System.DateTimeOffset | 要被人性化(humanize)的目标时刻,即需要被描述的那个时间点 |
comparisonBase | System.DateTimeOffset | 比较基准时刻,input与它之间的时间距离被转换为文字 |
culture | System.Globalization.CultureInfo? | 可空的文化信息;为null时使用当前线程的CultureInfo,影响最终措辞的本地化 |
返回值string即"距离的文字表述"。策略内部不应抛出异常或返回空串,而应始终返回一个符合目标文化习惯的短语。
调用链:扩展方法如何路由到策略
IDateTimeOffsetHumanizeStrategy不是被用户直接调用的,而是由DateTimeOffset.Humanize()扩展方法间接调用。在 src/Humanizer/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); }调用链为:input.Humanize(base, culture)→ 读取Configurator.DateTimeOffsetHumanizeStrategy当前实例 → 调用其Humanize(input, comparisonBase, culture)。
三个关键行为:
- 比较基准默认取当前 UTC 时刻:当
dateToCompareAgainst为null时,使用DateTimeOffset.UtcNow作为comparisonBase。这意味着 "now" 是 UTC 时间,与input的偏移量无关,任何带时区的时间戳都能与当前时间公平比较。 - 可空重载返回 "never":
DateTimeOffset?的重载在值为null时返回Configurator.GetFormatter(culture).DateHumanize_Never(),即各文化下的 "never" 表述,而不是走策略路径。测试 tests/Humanizer.Tests/DateTimeOffsetHumanizeTests.cs 验证了((DateTimeOffset?)null).Humanize()返回 "never"。 - culture 为 null 时的行为:策略内部通过
Configurator.GetFormatter(culture)解析,null文化会回落为当前线程文化。
内置实现一:DefaultDateTimeOffsetHumanizeStrategy
默认策略类定义在 src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeOffsetHumanizeStrategy.cs:
namespace Humanizer; /// <summary> /// The default 'distance of time' -> words calculator. /// </summary> public class DefaultDateTimeOffsetHumanizeStrategy : IDateTimeOffsetHumanizeStrategy { /// <summary> /// Calculates the distance of time in words between two provided dates /// </summary> public string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo? culture) => DateTimeHumanizeAlgorithms.DefaultHumanize(input.UtcDateTime, comparisonBase.UtcDateTime, culture); }它的实现极简:先把两个DateTimeOffset转成UtcDateTime(确保时区差异不会干扰计算),再委托给内部算法DateTimeHumanizeAlgorithms.DefaultHumanize。
默认算法:阶梯式区间映射
算法实现在 src/Humanizer/DateTimeHumanizeStrategy/DateTimeHumanizeAlgorithms.cs。核心是先根据input > comparisonBase判断时态(未来Tense.Future还是过去Tense.Past),然后按时间跨度大小逐级命中区间:
| 时间跨度条件 | 输出单位 | 说明 |
|---|---|---|
< 500 ms | 毫秒 | 输出 "now" 一类的即时表述 |
< 60 s | 秒 | 输出秒数 |
< 120 s | 分钟 | 固定输出 1 分钟("a minute ago" 风格) |
< 60 min | 分钟 | 输出分钟数 |
< 90 min | 小时 | 固定输出 1 小时 |
< 24 h | 小时 | 输出小时数 |
< 48 h | 天 | 以天计 |
< 7 d | 天 | 输出天数 |
< 28 d | 周 | 天数除以 7 得周数 |
28–30 d | 月/天 | 若处于相邻同月则输出 1 个月,否则输出天数 |
< 345 d | 月 | floor(总天数 / 29.5) |
| 其余 | 年 | floor(总天数 / 365),最小为 1 年 |
该算法针对"自然语言"习惯做了刻意设计:小于两分钟统一说 1 分钟、小于 90 分钟统一说 1 小时、28–30 天看是否同月决定说"1 个月"还是具体天数,这正是 Humanizer 输出读起来像人话而非生硬数字的原因。
默认策略的测试佐证
tests/Humanizer.Tests/DateTimeOffsetHumanizeTests.cs 中,2015-07-05 04:00 UTC对2015-07-05 03:00 UTC输出"an hour from now";偏移量不同的用例(tests/Humanizer.Tests/DateTimeOffsetHumanizeTests.cs中DefaultStrategy_DifferentOffsets)中,03:00 (+02:00)对02:30 (+01:00)输出"30 minutes ago"——注意两个时刻的绝对 UTC 时刻相差 30 分钟,这验证了策略先统一到 UTC 再计算的正确性。
内置实现二:PrecisionDateTimeOffsetHumanizeStrategy
精度策略类定义在 src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeOffsetHumanizeStrategy.cs:
namespace Humanizer; /// <summary> /// Precision-based calculator for distance between two times /// </summary> 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)
- 取值范围:
0到1之间的double,默认0.75。 - 语义:近似阈值系数。判定一个时间跨度"是否进位到下一单位"时,用
单位上限 × precision作为进位门槛。 - 效果:
precision越大,越容易把小跨度进位为大单位(例如 59.5 秒在precision = 0.75时进位为 1 分钟,因为59 × 0.75 = 44.25 < 59.5);precision越小,越倾向保留小单位。设置为1时行为接近四舍五入到最大整数单位。
精度算法的核心逻辑
算法实现在 src/Humanizer/DateTimeHumanizeStrategy/DateTimeHumanizeAlgorithms.cs,与默认算法的"区间阶梯"完全不同,采用从小单位向大单位逐级进位:
毫秒 >= 999 × precision → 秒 +1 秒 >= 59 × precision → 分 +1 分 >= 59 × precision → 时 +1 时 >= 23 × precision → 天 +1 天 >= 30 × precision 且 <= 31 → 月 = 1 天 > 31 且 < 365 × precision → 月 = floor(天/30) 或进位 +1 天 >= 365 × precision 且 <= 366 → 年 = 1 天 > 365 → 年 = floor(天/365) 或进位 +1随后从大单位向小单位反向输出,命中第一个非零单位即返回本地化短语:年 → 月 → 天 → 时 → 分 → 秒 → 毫秒。
精度策略的测试佐证
PrecisionStrategy_SameOffset:2015-07-05 04:00对比2015-07-04 05:00(相差 23 小时,precision = 0.75)输出"tomorrow"——因为23 ≥ 23 × 0.75,小时进位成 1 天。PrecisionStrategy_TwoMonthsAroundSixtyDays:1 月 27/28/29 日对比 3 月 29 日均输出"2 months ago"(见 tests/Humanizer.Tests/DateTimeOffsetHumanizeTests.cs)。- 多文化用例
Humanize_UsesSpecifiedCulture(同文件 tests/Humanizer.Tests/DateTimeOffsetHumanizeTests.cs)通过LocaleCoverageData.FormatterExpectationTheoryData遍历所有支持的文化,验证输出随CultureInfo本地化。
自定义策略:实现、挂载与使用
第一步:实现接口
以"只输出最大单位"的自定义策略为例:
using System.Globalization; using Humanizer; public class CoarseDateTimeOffsetHumanizeStrategy : IDateTimeOffsetHumanizeStrategy { public string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo? culture) { var diff = comparisonBase - input; var future = input > comparisonBase; // 只按最大单位输出 var (unit, count) = diff.TotalDays switch { >= 365 => (TimeUnit.Year, (int)(diff.TotalDays / 365)), >= 30 => (TimeUnit.Month, (int)(diff.TotalDays / 30)), >= 7 => (TimeUnit.Week, (int)(diff.TotalDays / 7)), >= 1 => (TimeUnit.Day, (int)diff.TotalDays), _ => (TimeUnit.Hour, (int)diff.TotalHours), }; var tense = future ? Tense.Future : Tense.Past; return Configurator.GetFormatter(culture).DateHumanize(unit, tense, count); } }注意:策略内部不应直接拼接英文单词,而应通过Configurator.GetFormatter(culture).DateHumanize(TimeUnit, Tense, int)生成短语,这样自定义策略天然获得全部文化支持(GetFormatter的解析逻辑见 src/Humanizer/Configuration/Configurator.cs)。
第二步:挂载到全局配置
在应用启动阶段(如Program.cs的Main方法最前面)设置:
Configurator.DateTimeOffsetHumanizeStrategy = new CoarseDateTimeOffsetHumanizeStrategy();第三步:使用
var input = DateTimeOffset.UtcNow.AddHours(-5); Console.WriteLine(input.Humanize()); // 输出如 "5 hours ago"Configurator:切换策略的官方入口
策略挂载点定义在 src/Humanizer/Configuration/Configurator.cs:
/// <summary> /// The strategy to be used for DateTimeOffset.Humanize /// </summary> public static IDateTimeOffsetHumanizeStrategy DateTimeOffsetHumanizeStrategy { get; set; } = new DefaultDateTimeOffsetHumanizeStrategy();三点使用注意事项:
- 默认实例:属性初始化即指向
DefaultDateTimeOffsetHumanizeStrategy,因此不配置也能获得开箱即用的默认人性化输出。 - 启动期设置:源码注释明确要求"only once during application startup before any humanization operations occur"——应在应用启动、任何 humanize 调用之前完成设置,避免运行期热切换带来的不一致。
- 线程安全:属性是可读写静态属性,在多线程场景(如 Web 应用请求处理)中并发读取/写入需要自行加同步或采用 volatile 读取;生产环境避免在服务运行中修改(见 src/Humanizer/Configuration/Configurator.cs 的 remarks)。
同类策略属性还包括DateTimeHumanizeStrategy(DateTime 中的DefaultHumanize/PrecisionHumanize多重重载)。
选择建议
| 需求 | 推荐策略 |
|---|---|
| 开箱即用的自然语言表述("an hour from now"、"2 weeks ago"),不关心精确秒数 | DefaultDateTimeOffsetHumanizeStrategy(默认) |
| 希望按时间跨度精确到小时/分钟,且可调进位阈值 | PrecisionDateTimeOffsetHumanizeStrategy,通过precision控制进位粒度 |
| 需要特定措辞风格、特定单位取舍或定制输出规则 | 自定义实现IDateTimeOffsetHumanizeStrategy并在启动期挂载 |
相关类型在 API 核准清单中均可见:tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt中列有public interface IDateTimeOffsetHumanizeStrategy、public class DefaultDateTimeOffsetHumanizeStrategy : Humanizer.IDateTimeOffsetHumanizeStrategy以及public PrecisionDateTimeOffsetHumanizeStrategy(double precision = 0.75),说明该接口与两个内置实现属于稳定的公开 API 面,可放心用于自定义扩展。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 中的 IDateTimeOffsetHumanizeStrategy 接口:自定义 DateTimeOffset.Humanize 人性化时间策略
Humanizer 中的 IDateTimeOffsetHumanizeStrategy 接口:自定义 DateTimeOffset.Humanize 人性化时
开发工具Humanizer 中 IDateTimeOffsetHumanizeStrategy 接口详解:自定义 DateTimeOffset.Humanize 日期人性化策略
Humanizer 中 IDateTimeOffsetHumanizeStrategy 接口详解:自定义 DateTimeOffset.Humanize 日期人
开发工具Humanizer 自定义 DateTimeOffset.Humanize 策略:IDateTimeOffsetHumanizeStrategy 接口深度解析
Humanizer 自定义 DateTimeOffset.Humanize 策略:IDateTimeOffsetHumanizeStrategy 接口深度解析
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考