diff --git a/readme.md b/readme.md
index ecdfe5870..b974e1c5a 100644
--- a/readme.md
+++ b/readme.md
@@ -38,6 +38,7 @@ Humanizer meets all your .NET needs for manipulating and displaying strings, enu
- [Number to Numbers](#number-to-numbers)
- [Number to words](#number-to-words)
- [Number to ordinal words](#number-to-ordinal-words)
+ - [Date to words](#date-to-words)
- [DateTime to ordinal words](#date-time-to-ordinal-words)
- [TimeOnly to Clock Notation](#time-only-to-clock-notation)
- [Roman numerals](#roman-numerals)
@@ -884,6 +885,25 @@ Passing `wordForm` argument in when it is not applicable will not make any diffe
43.ToOrdinalWords(GrammaticalGender.Masculine, WordForm.Abbreviation, new CultureInfo("en")) => "forty-third"
```
+### Date to words
+This is kind of an extension of ToWords and Ordinalize
+```C#
+// for Spanish locale
+new DateTime(2022, 1, 1).ToWords() => "uno de enero de dos mil veintidós"
+new DateOnly(2020, 6, 10).ToWords() => "diez de junio de dos mil veinte"
+new DateOnly(1999, 12, 31).ToWords() => "treinta y uno de diciembre de mil novecientos noventa y nueve"
+// for English UK locale
+new DateTime(2022, 1, 1).ToWords() => "the first of January two thousand and twenty-two"
+new DateOnly(1999, 12, 31).ToWords() => "the thirty-first of December one thousand nine hundred and ninety-nine"
+// for English US locale
+new DateTime(2022, 1, 1).ToWords() => "January first, two thousand and twenty-two"
+new DateOnly(1999, 12, 31).ToWords() => "December thirty-first, one thousand nine hundred and ninety-nine"
+```
+
+The ToWords method of `DateTime` or `DateOnly` also supports grammatical case.
+You can pass a second argument to `ToWords` to specify the case of the output.
+The possible values are `GrammaticalCase.Nominative`, `GrammaticalCase.Genitive`, `GrammaticalCase.Dative`, `GrammaticalCase.Accusative`, `GrammaticalCase.Instrumental` and `GrammaticalGender.Prepositional`
+
### DateTime to ordinal words
This is kind of an extension of Ordinalize
```C#
diff --git a/src/Humanizer.Tests.Shared/Localisation/en/DateToWordsTests.cs b/src/Humanizer.Tests.Shared/Localisation/en/DateToWordsTests.cs
new file mode 100644
index 000000000..6057ea642
--- /dev/null
+++ b/src/Humanizer.Tests.Shared/Localisation/en/DateToWordsTests.cs
@@ -0,0 +1,38 @@
+using System;
+using Xunit;
+
+namespace Humanizer.Tests.Localisation.en
+{
+ public class DateToWordsTests
+ {
+ [UseCulture("en-GB")]
+ [Fact]
+ public void ConvertDateToWordsGbString()
+ {
+ Assert.Equal("the first of January two thousand and twenty-two", new DateTime(2022, 1, 1).ToWords());
+ }
+
+ [UseCulture("en-US")]
+ [Fact]
+ public void ConvertDateToWordsUsString()
+ {
+ Assert.Equal("January first, two thousand and twenty-two", new DateTime(2022, 1, 1).ToWords());
+ }
+
+#if NET6_0_OR_GREATER
+ [UseCulture("en-GB")]
+ [Fact]
+ public void ConvertDateOnlyToWordsGbString()
+ {
+ Assert.Equal("the first of January two thousand and twenty-two", new DateOnly(2022, 1, 1).ToWords());
+ }
+
+ [UseCulture("en-US")]
+ [Fact]
+ public void ConvertDateOnlyToWordsUsString()
+ {
+ Assert.Equal("January first, two thousand and twenty-two", new DateOnly(2022, 1, 1).ToWords());
+ }
+#endif
+ }
+}
diff --git a/src/Humanizer.Tests.Shared/Localisation/es/DateToWordsTests.cs b/src/Humanizer.Tests.Shared/Localisation/es/DateToWordsTests.cs
new file mode 100644
index 000000000..6b1054a04
--- /dev/null
+++ b/src/Humanizer.Tests.Shared/Localisation/es/DateToWordsTests.cs
@@ -0,0 +1,29 @@
+using System;
+using Xunit;
+
+namespace Humanizer.Tests.Localisation.es
+{
+ [UseCulture("es-419")]
+ public class DateToWordsTests
+ {
+ [Fact]
+ public void ConvertDateToWordsString()
+ {
+ Assert.Equal("dos de enero de dos mil veintidós", new DateTime(2022, 1, 2).ToWords());
+ Assert.Equal("diez de junio de dos mil veinte", new DateTime(2020, 6, 10).ToWords());
+ Assert.Equal("veinticinco de septiembre de dos mil diecisiete", new DateTime(2017, 9, 25).ToWords());
+ Assert.Equal("treinta y uno de diciembre de mil novecientos noventa y nueve", new DateTime(1999, 12, 31).ToWords());
+ }
+
+#if NET6_0_OR_GREATER
+ [Fact]
+ public void ConvertDateOnlyToWordsString()
+ {
+ Assert.Equal("dos de enero de dos mil veintidós", new DateOnly(2022, 1, 2).ToWords());
+ Assert.Equal("diez de junio de dos mil veinte", new DateOnly(2020, 6, 10).ToWords());
+ Assert.Equal("veinticinco de septiembre de dos mil diecisiete", new DateOnly(2017, 9, 25).ToWords());
+ Assert.Equal("treinta y uno de diciembre de mil novecientos noventa y nueve", new DateOnly(1999, 12, 31).ToWords());
+ }
+#endif
+ }
+}
diff --git a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt
index 746484da8..959693a23 100644
--- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt
+++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt
@@ -92,6 +92,13 @@ namespace Humanizer
public static string ToOrdinalWords(this System.DateOnly input) { }
public static string ToOrdinalWords(this System.DateOnly input, Humanizer.GrammaticalCase grammaticalCase) { }
}
+ public class static DateToWordsExtensions
+ {
+ public static string ToWords(this System.DateTime input) { }
+ public static string ToWords(this System.DateTime input, Humanizer.GrammaticalCase grammaticalCase) { }
+ public static string ToWords(this System.DateOnly input) { }
+ public static string ToWords(this System.DateOnly input, Humanizer.GrammaticalCase grammaticalCase) { }
+ }
public class static EnglishArticle
{
public static string[] AppendArticlePrefix(string[] items) { }
@@ -1757,9 +1764,11 @@ namespace Humanizer.Configuration
public static Humanizer.Configuration.LocaliserRegistry CollectionFormatters { get; }
public static Humanizer.DateTimeHumanizeStrategy.IDateOnlyHumanizeStrategy DateOnlyHumanizeStrategy { get; set; }
public static Humanizer.Configuration.LocaliserRegistry DateOnlyToOrdinalWordsConverters { get; }
+ public static Humanizer.Configuration.LocaliserRegistry DateOnlyToWordsConverters { get; }
public static Humanizer.DateTimeHumanizeStrategy.IDateTimeHumanizeStrategy DateTimeHumanizeStrategy { get; set; }
public static Humanizer.DateTimeHumanizeStrategy.IDateTimeOffsetHumanizeStrategy DateTimeOffsetHumanizeStrategy { get; set; }
public static Humanizer.Configuration.LocaliserRegistry DateToOrdinalWordsConverters { get; }
+ public static Humanizer.Configuration.LocaliserRegistry DateToWordsConverters { get; }
public static System.Func EnumDescriptionPropertyLocator { get; set; }
public static Humanizer.Configuration.LocaliserRegistry Formatters { get; }
public static Humanizer.Configuration.LocaliserRegistry NumberToWordsConverters { get; }
@@ -1928,6 +1937,19 @@ namespace Humanizer.Localisation.DateToOrdinalWords
string Convert(System.DateTime date, Humanizer.GrammaticalCase grammaticalCase);
}
}
+namespace Humanizer.Localisation.DateToWords
+{
+ public interface IDateOnlyToWordConverter
+ {
+ string Convert(System.DateOnly date);
+ string Convert(System.DateOnly date, Humanizer.GrammaticalCase grammaticalCase);
+ }
+ public interface IDateToWordConverter
+ {
+ string Convert(System.DateTime date);
+ string Convert(System.DateTime date, Humanizer.GrammaticalCase grammaticalCase);
+ }
+}
namespace Humanizer.Localisation.Formatters
{
public class DefaultFormatter : Humanizer.Localisation.Formatters.IFormatter
diff --git a/src/Humanizer/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs
index 157d76865..f0d214db5 100644
--- a/src/Humanizer/Configuration/Configurator.cs
+++ b/src/Humanizer/Configuration/Configurator.cs
@@ -4,6 +4,7 @@
using Humanizer.DateTimeHumanizeStrategy;
using Humanizer.Localisation.CollectionFormatters;
using Humanizer.Localisation.DateToOrdinalWords;
+using Humanizer.Localisation.DateToWords;
using Humanizer.Localisation.Formatters;
using Humanizer.Localisation.NumberToWords;
using Humanizer.Localisation.Ordinalizers;
@@ -64,6 +65,15 @@ public static LocaliserRegistry DateToOrdinalWordsC
get { return _dateToOrdinalWordConverters; }
}
+ private static readonly LocaliserRegistry _dateToWordConverters = new DateToWordsConverterRegistry();
+ ///
+ /// A registry of date to words converters used to localise ToWords method
+ ///
+ public static LocaliserRegistry DateToWordsConverters
+ {
+ get { return _dateToWordConverters; }
+ }
+
#if NET6_0_OR_GREATER
private static readonly LocaliserRegistry _dateOnlyToOrdinalWordConverters = new DateOnlyToOrdinalWordsConverterRegistry();
///
@@ -74,6 +84,15 @@ public static LocaliserRegistry DateOnlyToOrdin
get { return _dateOnlyToOrdinalWordConverters; }
}
+ private static readonly LocaliserRegistry _dateOnlyToWordConverters = new DateOnlyToWordsConverterRegistry();
+ ///
+ /// A registry of date to words converters used to localise ToWords method
+ ///
+ public static LocaliserRegistry DateOnlyToWordsConverters
+ {
+ get { return _dateOnlyToWordConverters; }
+ }
+
private static readonly LocaliserRegistry _timeOnlyToClockNotationConverters = new TimeOnlyToClockNotationConvertersRegistry();
///
/// A registry of time to clock notation converters used to localise ToClockNotation methods
@@ -132,6 +151,17 @@ internal static IDateToOrdinalWordConverter DateToOrdinalWordsConverter
}
}
+ ///
+ /// The converter of date to Words to be used
+ ///
+ internal static IDateToWordConverter DateToWordsConverter
+ {
+ get
+ {
+ return DateToWordsConverters.ResolveForUiCulture();
+ }
+ }
+
#if NET6_0_OR_GREATER
///
/// The ordinalizer to be used
@@ -151,6 +181,25 @@ internal static ITimeOnlyToClockNotationConverter TimeOnlyToClockNotationConvert
return TimeOnlyToClockNotationConverters.ResolveForUiCulture();
}
}
+
+ ///
+ /// The converter of date to Words to be used
+ ///
+ internal static IDateOnlyToWordConverter DateOnlyToWordsConverter
+ {
+ get
+ {
+ return DateOnlyToWordsConverters.ResolveForUiCulture();
+ }
+ }
+
+ //internal static ITimeOnlyToClockNotationConverter TimeOnlyToClockNotationConverter
+ //{
+ // get
+ // {
+ // return TimeOnlyToClockNotationConverters.ResolveForUiCulture();
+ // }
+ //}
#endif
private static IDateTimeHumanizeStrategy _dateTimeHumanizeStrategy = new DefaultDateTimeHumanizeStrategy();
diff --git a/src/Humanizer/Configuration/DateOnlyToWordsConverterRegistry.cs b/src/Humanizer/Configuration/DateOnlyToWordsConverterRegistry.cs
new file mode 100644
index 000000000..b5f7f82c0
--- /dev/null
+++ b/src/Humanizer/Configuration/DateOnlyToWordsConverterRegistry.cs
@@ -0,0 +1,15 @@
+#if NET6_0_OR_GREATER
+using Humanizer.Localisation.DateToWords;
+
+namespace Humanizer.Configuration
+{
+ internal class DateOnlyToWordsConverterRegistry : LocaliserRegistry
+ {
+ public DateOnlyToWordsConverterRegistry() : base(new DefaultDateOnlyToWordConverter())
+ {
+ Register("en-US", new UsDateOnlyToWordsConverter());
+ Register("es", new EsDateOnlyToWordsConverter());
+ }
+ }
+}
+#endif
diff --git a/src/Humanizer/Configuration/DateToWordsConverterRegistry.cs b/src/Humanizer/Configuration/DateToWordsConverterRegistry.cs
new file mode 100644
index 000000000..9a2cb2fd0
--- /dev/null
+++ b/src/Humanizer/Configuration/DateToWordsConverterRegistry.cs
@@ -0,0 +1,13 @@
+using Humanizer.Localisation.DateToWords;
+
+namespace Humanizer.Configuration
+{
+ internal class DateToWordsConverterRegistry : LocaliserRegistry
+ {
+ public DateToWordsConverterRegistry() : base(new DefaultDateToWordConverter())
+ {
+ Register("en-US", new UsDateToWordsConverter());
+ Register("es", new EsDateToWordsConverter());
+ }
+ }
+}
diff --git a/src/Humanizer/DateToWordsExtensions.cs b/src/Humanizer/DateToWordsExtensions.cs
new file mode 100644
index 000000000..c0a727e07
--- /dev/null
+++ b/src/Humanizer/DateToWordsExtensions.cs
@@ -0,0 +1,53 @@
+using System;
+using Humanizer.Configuration;
+
+namespace Humanizer
+{
+ ///
+ /// Humanizes DateTime into human readable sentence
+ ///
+ public static class DateToWordsExtensions
+ {
+ ///
+ /// Turns the provided date into words
+ ///
+ /// The date to be made into words
+ /// The date in words
+ public static string ToWords(this DateTime input)
+ {
+ return Configurator.DateToWordsConverter.Convert(input);
+ }
+ ///
+ /// Turns the provided date into words
+ ///
+ /// The date to be made into words
+ /// The grammatical case to use for output words
+ /// The date in words
+ public static string ToWords(this DateTime input, GrammaticalCase grammaticalCase)
+ {
+ return Configurator.DateToWordsConverter.Convert(input, grammaticalCase);
+ }
+
+#if NET6_0_OR_GREATER
+ ///
+ /// Turns the provided date into words
+ ///
+ /// The date to be made into words
+ /// The date in words
+ public static string ToWords(this DateOnly input)
+ {
+ return Configurator.DateOnlyToWordsConverter.Convert(input);
+ }
+ ///
+ /// Turns the provided date into words
+ ///
+ /// The date to be made into words
+ /// The grammatical case to use for output words
+ /// The date in words
+ public static string ToWords(this DateOnly input, GrammaticalCase grammaticalCase)
+ {
+ return Configurator.DateOnlyToWordsConverter.Convert(input, grammaticalCase);
+ }
+#endif
+ }
+}
diff --git a/src/Humanizer/Localisation/DateToWords/DefaultDateOnlyToWordConverter.cs b/src/Humanizer/Localisation/DateToWords/DefaultDateOnlyToWordConverter.cs
new file mode 100644
index 000000000..aac84b64b
--- /dev/null
+++ b/src/Humanizer/Localisation/DateToWords/DefaultDateOnlyToWordConverter.cs
@@ -0,0 +1,23 @@
+#if NET6_0_OR_GREATER
+
+using System;
+
+namespace Humanizer.Localisation.DateToWords
+{
+ internal class DefaultDateOnlyToWordConverter : IDateOnlyToWordConverter
+ {
+
+ public virtual string Convert(DateOnly date)
+ {
+ return "the " + date.Day.ToOrdinalWords() + date.ToString(" 'of' MMMM ") + date.Year.ToWords();
+ }
+
+ public virtual string Convert(DateOnly date, GrammaticalCase grammaticalCase)
+ {
+ return Convert(date);
+ }
+
+ }
+}
+
+#endif
diff --git a/src/Humanizer/Localisation/DateToWords/DefaultDateToWordConverter.cs b/src/Humanizer/Localisation/DateToWords/DefaultDateToWordConverter.cs
new file mode 100644
index 000000000..b2cbec01f
--- /dev/null
+++ b/src/Humanizer/Localisation/DateToWords/DefaultDateToWordConverter.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Humanizer.Localisation.DateToWords
+{
+ internal class DefaultDateToWordConverter : IDateToWordConverter
+ {
+
+ public virtual string Convert(DateTime date)
+ {
+ return "the " + date.Day.ToOrdinalWords() + date.ToString(" 'of' MMMM ") + date.Year.ToWords();
+ }
+
+ public virtual string Convert(DateTime date, GrammaticalCase grammaticalCase)
+ {
+ return Convert(date);
+ }
+
+ }
+}
diff --git a/src/Humanizer/Localisation/DateToWords/EsDateOnlyToWordsConverter.cs b/src/Humanizer/Localisation/DateToWords/EsDateOnlyToWordsConverter.cs
new file mode 100644
index 000000000..0c3a92cd2
--- /dev/null
+++ b/src/Humanizer/Localisation/DateToWords/EsDateOnlyToWordsConverter.cs
@@ -0,0 +1,18 @@
+#if NET6_0_OR_GREATER
+
+using System;
+using Humanizer.Configuration;
+
+namespace Humanizer.Localisation.DateToWords
+{
+ internal class EsDateOnlyToWordsConverter : DefaultDateOnlyToWordConverter
+ {
+ public override string Convert(DateOnly date)
+ {
+ var equivalentDateTime = date.ToDateTime(TimeOnly.MinValue);
+ return Configurator.DateToWordsConverter.Convert(equivalentDateTime);
+ }
+ }
+}
+
+#endif
diff --git a/src/Humanizer/Localisation/DateToWords/EsDateToWordsConverter.cs b/src/Humanizer/Localisation/DateToWords/EsDateToWordsConverter.cs
new file mode 100644
index 000000000..12e17fd51
--- /dev/null
+++ b/src/Humanizer/Localisation/DateToWords/EsDateToWordsConverter.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Humanizer.Localisation.DateToWords
+{
+ internal class EsDateToWordsConverter : DefaultDateToWordConverter
+ {
+ public override string Convert(DateTime date)
+ {
+ return date.Day.ToWords() + date.ToString(" 'de' MMMM 'de' ") + date.Year.ToWords();
+ }
+ }
+}
diff --git a/src/Humanizer/Localisation/DateToWords/IDateOnlyToWordConverter.cs b/src/Humanizer/Localisation/DateToWords/IDateOnlyToWordConverter.cs
new file mode 100644
index 000000000..724e676d4
--- /dev/null
+++ b/src/Humanizer/Localisation/DateToWords/IDateOnlyToWordConverter.cs
@@ -0,0 +1,28 @@
+#if NET6_0_OR_GREATER
+
+using System;
+
+namespace Humanizer.Localisation.DateToWords
+{
+ ///
+ /// The interface used to localise the ToWords method.
+ ///
+ public interface IDateOnlyToWordConverter
+ {
+ ///
+ /// Converts the date to Words
+ ///
+ ///
+ ///
+ string Convert(DateOnly date);
+
+ ///
+ /// Converts the date to Words using the provided grammatical case
+ ///
+ ///
+ ///
+ ///
+ string Convert(DateOnly date, GrammaticalCase grammaticalCase);
+ }
+}
+#endif
diff --git a/src/Humanizer/Localisation/DateToWords/IDateToWordConverter.cs b/src/Humanizer/Localisation/DateToWords/IDateToWordConverter.cs
new file mode 100644
index 000000000..1ae806dc9
--- /dev/null
+++ b/src/Humanizer/Localisation/DateToWords/IDateToWordConverter.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Humanizer.Localisation.DateToWords
+{
+ ///
+ /// The interface used to localise the ToWords method.
+ ///
+ public interface IDateToWordConverter
+ {
+ ///
+ /// Converts the date to Words
+ ///
+ ///
+ ///
+ string Convert(DateTime date);
+
+ ///
+ /// Converts the date to Words using the provided grammatical case
+ ///
+ ///
+ ///
+ ///
+ string Convert(DateTime date, GrammaticalCase grammaticalCase);
+ }
+}
diff --git a/src/Humanizer/Localisation/DateToWords/UsDateOnlyToWordsConverter.cs b/src/Humanizer/Localisation/DateToWords/UsDateOnlyToWordsConverter.cs
new file mode 100644
index 000000000..a3cafd742
--- /dev/null
+++ b/src/Humanizer/Localisation/DateToWords/UsDateOnlyToWordsConverter.cs
@@ -0,0 +1,18 @@
+#if NET6_0_OR_GREATER
+
+using System;
+using Humanizer.Configuration;
+
+namespace Humanizer.Localisation.DateToWords
+{
+ internal class UsDateOnlyToWordsConverter : DefaultDateOnlyToWordConverter
+ {
+ public override string Convert(DateOnly date)
+ {
+ var equivalentDateTime = date.ToDateTime(TimeOnly.MinValue);
+ return Configurator.DateToWordsConverter.Convert(equivalentDateTime);
+ }
+ }
+}
+
+#endif
diff --git a/src/Humanizer/Localisation/DateToWords/UsDateToWordsConverter.cs b/src/Humanizer/Localisation/DateToWords/UsDateToWordsConverter.cs
new file mode 100644
index 000000000..51f4a5bea
--- /dev/null
+++ b/src/Humanizer/Localisation/DateToWords/UsDateToWordsConverter.cs
@@ -0,0 +1,14 @@
+using System;
+
+using Humanizer.Localisation.DateToWords;
+
+namespace Humanizer.Localisation.DateToWords
+{
+ internal class UsDateToWordsConverter : DefaultDateToWordConverter
+ {
+ public override string Convert(DateTime date)
+ {
+ return date.ToString("MMMM ") + date.Day.ToOrdinalWords() + ", " + date.Year.ToWords();
+ }
+ }
+}