☰
Humanizer 日期序数词转换指南:深入理解 IDateToOrdinalWordConverter 接口与本地化实现
2026/9/28 22:18:57 网站建设 项目流程
  • 开发工具

【免费下载链接】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 中负责将DateTime转换为序数词日期文本的IDateToOrdinalWordConverter接口展开,系统讲解其两个Convert方法的重载语义、默认实现的本地化行为、基于 YAML 语言配置的注册机制,以及自定义转换器的注册方式。读完本文,你将掌握ToOrdinalWords扩展方法的底层调用链,理解英文与非英文文化下序数日期的不同渲染策略,并能根据业务需要接入自己的日期序数词转换实现。

接口定位:本地化ToOrdinalWords的转换契约

IDateToOrdinalWordConverter是 Humanizer 中“把日期转成序数词文本”这一能力的抽象契约,其 XML 文档注释明确指出:The interface used to localise the ToOrdinalWords method(用于本地化ToOrdinalWords方法的接口)。也就是说,所有文化(Culture)相关的序数日期文本生成逻辑,都收敛到这一个接口之下,由它统一承载。

接口定义位于 src/Humanizer/Localisation/DateToOrdinalWords/IDateToOrdinalWordConverter.cs,完整签名如下:

namespace Humanizer; /// <summary> /// Converts dates into the localized text used by <c>ToOrdinalWords</c>. /// </summary> public interface IDateToOrdinalWordConverter { /// <summary> /// Converts the given <paramref name="date"/> to ordinal words for the current culture. /// </summary> string Convert(DateTime date); /// <summary> /// Converts the given <paramref name="date"/> to ordinal words using the specified grammatical case. /// </summary> string Convert(DateTime date, GrammaticalCase grammaticalCase); }

与DateOnly(.NET 6+)对应的IDateOnlyToOrdinalWordConverter接口同位于 src/Humanizer/Localisation/DateToOrdinalWords/IDateOnlyToOrdinalWordConverter.cs,二者结构对称,本文以IDateToOrdinalWordConverter为主线展开。

Convert(DateTime):按当前文化输出序数日期

string Convert(System.DateTime date);
  • 参数:date,类型为System.DateTime,即待转换的日期值。
  • 返回值:System.String,即本地化的序数日期文本。

该方法不接收任何文化参数,实际文化由实现内部决定——默认实现读取的是CultureInfo.CurrentCulture(详见下文“默认实现”一节),因此输出的语言与格式随线程当前文化而变化。

Convert(DateTime, GrammaticalCase):按指定语法格输出序数日期

string Convert(System.DateTime date, Humanizer.GrammaticalCase grammaticalCase);
  • 参数:
    • date:System.DateTime,待转换日期;
    • grammaticalCase:Humanizer.GrammaticalCase,希望应用于输出文本的语法格(如主格 Nominative、属格 Genitive 等)。
  • 返回值:System.String,本地化序数日期文本。

该重载主要为拥有格位系统的语言(如俄语、波兰语、乌克兰语等)服务:同一日期在不同语法格下需要不同的词形。GrammaticalCase枚举定义于 src/Humanizer/GrammaticalCase.cs,对于没有语法格概念的语言(如英语),此参数不会产生任何效果——默认实现会直接忽略它并委托给无参重载。

入口:ToOrdinalWords 扩展方法与配置中枢

IDateToOrdinalWordConverter并不直接暴露给调用方,日常使用是通过 src/Humanizer/DateToOrdinalWordsExtensions.cs 中的ToOrdinalWords扩展方法触发,例如:

new DateTime(2023, 1, 1).ToOrdinalWords() // en-US 下 => "1st of January, 2023" new DateTime(2020, 12, 22).ToOrdinalWords() // en-US 下 => "22nd of December, 2020"

其实现非常简洁,直接委托给全局配置中枢:

public static string ToOrdinalWords(this DateTime input) => Configurator.DateToOrdinalWordsConverter.Convert(input); public static string ToOrdinalWords(this DateTime input, GrammaticalCase grammaticalCase) => Configurator.DateToOrdinalWordsConverter.Convert(input, grammaticalCase);

这里的关键是Configurator。在 src/Humanizer/Configuration/Configurator.cs 中可以看到,日期序数词转换器与其他本地化组件一样,被组织在一个LocaliserRegistry注册表里:

public static LocaliserRegistry<IDateToOrdinalWordConverter> DateToOrdinalWordsConverters { get; } = new DateToOrdinalWordsConverterRegistry(); internal static IDateToOrdinalWordConverter DateToOrdinalWordsConverter => DateToOrdinalWordsConverters.ResolveForCulture(null);

ResolveForCulture(null)表示按当前线程文化解析(null即CultureInfo.CurrentCulture),因此ToOrdinalWords的输出始终跟随运行线程的文化设置。整个调用链可概括为:

DateTime.ToOrdinalWords() -> Configurator.DateToOrdinalWordsConverter(按当前文化解析) -> DateToOrdinalWordsConverterRegistry(注册表) -> IDateToOrdinalWordConverter.Convert(date[, grammaticalCase])

注册表与默认实现

注册表结构

src/Humanizer/Configuration/DateToOrdinalWordsConverterRegistry.cs 定义了该接口的注册表:

class DateToOrdinalWordsConverterRegistry : LocaliserRegistry<IDateToOrdinalWordConverter> { public DateToOrdinalWordsConverterRegistry() : base(_ => new DefaultDateToOrdinalWordConverter()) => DateToOrdinalWordsConverterRegistryRegistrations.Register(this); }

它继承自泛型基类LocaliserRegistry<TLocaliser>(见 src/Humanizer/Configuration/LocaliserRegistry.cs),并以DefaultDateToOrdinalWordConverter作为兜底默认实现——任何未显式注册的文化都会回落到它。而DateToOrdinalWordsConverterRegistryRegistrations.Register是 Humanizer 源生成器(Source Generator)根据语言 YAML 配置自动生成的一部分,它把各文化的专用转换器按 locale 代码注册进注册表,实现了“配置驱动”的本地化扩展。

LocaliserRegistry<TLocaliser>提供了几个关键能力:

  • Register(string localeCode, TLocaliser localiser):注册指定 locale 的转换器实例;
  • Register(string localeCode, Func<CultureInfo, TLocaliser> localiser):注册按文化工厂创建的转换器;
  • ResolveForCulture(CultureInfo? culture):解析指定文化对应的转换器,null表示当前线程文化;
  • 解析时先按精确文化名匹配,再沿culture.Parent链向上回退,最终落到默认实现。

默认实现:DefaultDateToOrdinalWordConverter

src/Humanizer/Localisation/DateToOrdinalWords/DefaultDateToOrdinalWordConverter.cs 是默认转换器,其行为可概括为两条规则:

class DefaultDateToOrdinalWordConverter : IDateToOrdinalWordConverter { const char LeftToRightMark = (char)0x200E; const char RightToLeftMark = (char)0x200F; const char ArabicLetterMark = (char)0x061C; public virtual string Convert(DateTime date) { var culture = CultureInfo.CurrentCulture; if (culture.TwoLetterISOLanguageName != "en") { // 非英语文化:直接采用该文化自身的短日期模式("d"), // 并剥离日历格式化时嵌入的方向性控制字符(LRM/RLM/ALM), // 使结果嵌入更大的序数短语时保持可读。 return SanitizeNonEnglishDate(date.ToString("d", culture)); } return date.Day.Ordinalize() + date.ToString(" MMMM yyyy"); } public virtual string Convert(DateTime date, GrammaticalCase grammaticalCase) => Convert(date); static string SanitizeNonEnglishDate(string value) => value.Replace(LeftToRightMark.ToString(), string.Empty) .Replace(RightToLeftMark.ToString(), string.Empty) .Replace(ArabicLetterMark.ToString(), string.Empty); }

要点拆解:

  1. 英语文化(en):输出形式为「序数日 + 空格 + 月份全名 + 空格 + 年份」,例如22nd December 2020(实际链接到ToOrdinalWords扩展方法后还带有 “of” 介词,见下文示例)。日部分由date.Day.Ordinalize()生成,即复用 Humanizer 的序数化能力。
  2. 非英语文化:不做“翻译式”处理,而是直接采用该文化自身的短日期格式字符串"d"(例如ru-RU的dd.MM.yyyy),随后做一次“清洗”——移除格式化输出中可能嵌入的 Unicode 方向性控制字符:U+200E(LRM,左至右标记)、U+200F(RLM,右至左标记)与U+061C(ALM,阿拉伯字母标记)。这是因为部分日历(尤其阿拉伯、希伯来等 RTL 日历)在短日期输出中会内嵌这些控制符,剥离后文本在嵌入更大句子的场景下不会干扰阅读顺序。
  3. 语法格重载:默认实现中Convert(date, grammaticalCase)直接忽略语法格参数并调用无参版本,注释明确说明这是因为回退转换器不随语法格变化词形——语法格支持由各语言的专用转换器提供。

模式化转换器:PatternDateToOrdinalWordsConverter

除了默认实现,还有基于预构建模式(pattern)的专用转换器 src/Humanizer/Localisation/DateToOrdinalWords/PatternDateToOrdinalWordsConverter.cs:

class PatternDateToOrdinalWordsConverter(OrdinalDatePattern pattern) : DefaultDateToOrdinalWordConverter { readonly OrdinalDatePattern pattern = pattern; public override string Convert(DateTime date) => pattern.Format(date); }

它继承默认实现并重写Convert(DateTime),将格式化工作委托给 src/Humanizer/Localisation/DateToOrdinalWords/OrdinalDatePattern.cs 中定义的OrdinalDatePattern。这是源生成器按语言配置产出各文化转换器的核心载体,详见下一节。

语言配置驱动:YAML 中的 ordinal.date 与源码生成

Humanizer 的本地化数据以 YAML 形式存放于 src/Humanizer/Locales 目录(如en.yml、ru.yml、zh-CN.yml等),由Humanizer.SourceGenerators在编译期读取并生成注册代码(DateToOrdinalWordsConverterRegistryRegistrations即由此产出)。

以 src/Humanizer/Locales/en.yml 为例,英语的序数日期配置为:

ordinal: date: engine: 'pattern' pattern: '{day} MMMM yyyy' dayMode: 'Ordinal' dateOnly: engine: 'pattern' pattern: '{day} MMMM yyyy' dayMode: 'Ordinal'

而以 src/Humanizer/Locales/ru.yml 为例,俄语的配置则把日期渲染为纯数字模式:

ordinal: date: engine: 'pattern' pattern: '{day} MMMM yyyy' dayMode: 'Numeric' dateOnly: engine: 'pattern' pattern: '{day} MMMM yyyy' dayMode: 'Numeric'

可以看到配置中有三个关键字段:

字段取值示例含义
enginepattern指定转换器的生成引擎类型;目前日期序数词走pattern模式,即生成PatternDateToOrdinalWordsConverter
pattern{day} MMMM yyyy日期格式模板,其中{day}是日的占位符,其余部分遵循 .NET 自定义日期格式说明符
dayModeOrdinal/Numeric等日部分的渲染模式

dayMode的完整取值定义在 src/Humanizer/Localisation/DateToOrdinalWords/OrdinalDatePattern.cs 的OrdinalDateDayMode枚举中:

enum OrdinalDateDayMode { /// <summary>Renders the day as a culture-aware numeric value.</summary> Numeric, /// <summary>Renders the day as an ordinal word.</summary> Ordinal, /// <summary>Renders the day as a numeric value except for the first day of the month.</summary> OrdinalWhenDayIsOne, /// <summary>Renders the first day of the month using the masculine ordinal form.</summary> MasculineOrdinalWhenDayIsOne, /// <summary>Renders the day as a numeric value followed by a dot suffix.</summary> DotSuffix }

各模式的渲染逻辑对应OrdinalDatePattern.FormatDay(int day)的 switch 分支:Numeric输出本地化数字;Ordinal调用day.Ordinalize();OrdinalWhenDayIsOne仅在日为 1 时输出序数词;MasculineOrdinalWhenDayIsOne则在日为 1 时输出阳性序数形式(day.Ordinalize(GrammaticalGender.Masculine));DotSuffix输出数字加英文句点(如德语风格的1.)。

此外OrdinalDatePattern还承担了若干精细工作:

  • 日历兼容:OrdinalDateCalendarMode.Gregorian模式下会把文化日历强制切换为本地化公历(GregorianCalendar),保证年份输出一致;Native模式则保留文化原生日历(如泰历)。
  • 属格月份:对斯拉夫语系等存在属格(genitive)月份词形的语言,当模式中“日与月相邻”(如{day} MMMM)时,会替换为monthsGenitive数组中的属格月名(参见SubstituteMonth与IsDayAdjacentToMonth的实现)。
  • Hijri 日历月份:支持hijriMonths覆盖数组,用于HijriCalendar/UmAlQuraCalendar下的月名替换(ResolveMonthArray)。
  • 方向性控制符剥离:与默认实现一样,最终输出会去除\u200E、\u200F、\u061C等控制字符(StripDirectionalityControls)。

从源生成器一侧看,src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/OrdinalDateProfileCatalogInput.cs 负责读取上述 YAML 配置并生成对应的注册表达式:engine为pattern时,产出new PatternDateToOrdinalWordsConverter(new OrdinalDatePattern(...))形式的注册代码(CreatePatternExpression),并把pattern、dayMode、calendarMode与月份数组等参数逐一映射进OrdinalDatePattern构造器。

自定义转换器:注册与替换

IDateToOrdinalWordConverter作为一个公开接口,允许使用者注入自己的实现。标准做法是通过Configurator.DateToOrdinalWordsConverters注册表注册:

using Humanizer; public sealed class MyDateToOrdinalWordConverter : IDateToOrdinalWordConverter { public string Convert(DateTime date) => $"Day {date.Day} of month {date.ToString("MMMM", System.Globalization.CultureInfo.CurrentCulture)} in {date.Year}"; public string Convert(DateTime date, GrammaticalCase grammaticalCase) => Convert(date); } // 应用启动阶段(如 Main 方法或 ModuleInitializer)注册: Configurator.DateToOrdinalWordsConverters.Register("en-US", new MyDateToOrdinalWordConverter()); // 或使用工厂形式: Configurator.DateToOrdinalWordsConverters.Register("fr-FR", _ => new MyDateToOrdinalWordConverter());

注册后,在对应文化下调用new DateTime(2023, 5, 17).ToOrdinalWords()就会走自定义实现。需要注意两点:

  1. 注册时机:LocaliserRegistry.Register在注册表首次被解析(即首次调用ToOrdinalWords)之后会抛InvalidOperationException(“Cannot register localisers after the registry has been used.”),因为注册表在首次使用时会将字典“冻结”为FrozenDictionary以提升读取性能。因此自定义注册必须放在应用启动阶段、任何ToOrdinalWords调用之前。
  2. 回退链:未注册的文化会沿culture.Parent链逐级回退(如en-US未注册可回退到en),最终落到DefaultDateToOrdinalWordConverter。若需要全局替换所有文化的行为,可参考LocaliserRegistry的默认实现构造函数,把默认转换器替换为自定义类型。

行为验证与测试参考

仓库测试集Humanizer.Tests中与本主题相关的测试(如 tests/Humanizer.Tests/DateToOrdinalWordsExtensions.cs 及本地化目录下的日期序数测试)验证了以下行为:

  • 英语文化下DateTime输出包含序数词(st/nd/rd/th)的日期短语;
  • 非英语文化(如ru、de、it等)输出遵循其文化短日期格式;
  • GrammaticalCase重载在支持格位系统的语言上产生不同词形,在英语上无差异;
  • 剥离方向性控制字符后,RTL 日历输出不携带干扰阅读的嵌入标记。

如需扩充测试或本地化数据,可对照 src/Humanizer/Locales 下的 YAML 配置与 src/Humanizer.SourceGenerators 的生成逻辑;完整的 API 文档快照见 website/versioned_docs/version-3.0.1/api/Humanizer.IDateToOrdinalWordConverter.md。

小结

IDateToOrdinalWordConverter是 Humanizer 日期序数词本地化的核心抽象:Convert(DateTime)负责按当前文化生成序数日期文本,Convert(DateTime, GrammaticalCase)在支持格位系统的语言上提供语法格变体。其背后是由Configurator注册表、YAML 语言配置、源生成器与OrdinalDatePattern模式引擎构成的完整流水线——英语走“序数词 + 月份 + 年份”,其余文化走“文化短日期 + 方向性控制符清洗”。理解这一接口与它的默认实现、注册机制,既能帮助你正确使用ToOrdinalWords,也为定制多语言日期呈现提供了明确的扩展点。

  • 开发工具

【免费下载链接】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
点击查看免费下载
上一篇:CesiumJS能源地图:发电设施与能源消费可视化完整指南
下一篇:从零开始编写你的第一个vis编辑器Lua插件:完整开发指南

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询