Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DateToWords Feature #1210

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions readme.md
Expand Up @@ -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)
Expand Down Expand Up @@ -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"
```

### <a id="date-to-words">Date to words</a>
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`

### <a id="date-time-to-ordinal-words">DateTime to ordinal words</a>
This is kind of an extension of Ordinalize
```C#
Expand Down
38 changes: 38 additions & 0 deletions 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
}
}
29 changes: 29 additions & 0 deletions 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
}
}
Expand Up @@ -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) { }
Expand Down Expand Up @@ -1757,9 +1764,11 @@ namespace Humanizer.Configuration
public static Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.CollectionFormatters.ICollectionFormatter> CollectionFormatters { get; }
public static Humanizer.DateTimeHumanizeStrategy.IDateOnlyHumanizeStrategy DateOnlyHumanizeStrategy { get; set; }
public static Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.DateToOrdinalWords.IDateOnlyToOrdinalWordConverter> DateOnlyToOrdinalWordsConverters { get; }
public static Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.DateToWords.IDateOnlyToWordConverter> DateOnlyToWordsConverters { get; }
public static Humanizer.DateTimeHumanizeStrategy.IDateTimeHumanizeStrategy DateTimeHumanizeStrategy { get; set; }
public static Humanizer.DateTimeHumanizeStrategy.IDateTimeOffsetHumanizeStrategy DateTimeOffsetHumanizeStrategy { get; set; }
public static Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.DateToOrdinalWords.IDateToOrdinalWordConverter> DateToOrdinalWordsConverters { get; }
public static Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.DateToWords.IDateToWordConverter> DateToWordsConverters { get; }
public static System.Func<System.Reflection.PropertyInfo, bool> EnumDescriptionPropertyLocator { get; set; }
public static Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.Formatters.IFormatter> Formatters { get; }
public static Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.NumberToWords.INumberToWordsConverter> NumberToWordsConverters { get; }
Expand Down Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions src/Humanizer/Configuration/Configurator.cs
Expand Up @@ -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;
Expand Down Expand Up @@ -64,6 +65,15 @@ public static LocaliserRegistry<IDateToOrdinalWordConverter> DateToOrdinalWordsC
get { return _dateToOrdinalWordConverters; }
}

private static readonly LocaliserRegistry<IDateToWordConverter> _dateToWordConverters = new DateToWordsConverterRegistry();
/// <summary>
/// A registry of date to words converters used to localise ToWords method
/// </summary>
public static LocaliserRegistry<IDateToWordConverter> DateToWordsConverters
{
get { return _dateToWordConverters; }
}

#if NET6_0_OR_GREATER
private static readonly LocaliserRegistry<IDateOnlyToOrdinalWordConverter> _dateOnlyToOrdinalWordConverters = new DateOnlyToOrdinalWordsConverterRegistry();
/// <summary>
Expand All @@ -74,6 +84,15 @@ public static LocaliserRegistry<IDateOnlyToOrdinalWordConverter> DateOnlyToOrdin
get { return _dateOnlyToOrdinalWordConverters; }
}

private static readonly LocaliserRegistry<IDateOnlyToWordConverter> _dateOnlyToWordConverters = new DateOnlyToWordsConverterRegistry();
/// <summary>
/// A registry of date to words converters used to localise ToWords method
/// </summary>
public static LocaliserRegistry<IDateOnlyToWordConverter> DateOnlyToWordsConverters
{
get { return _dateOnlyToWordConverters; }
}

private static readonly LocaliserRegistry<ITimeOnlyToClockNotationConverter> _timeOnlyToClockNotationConverters = new TimeOnlyToClockNotationConvertersRegistry();
/// <summary>
/// A registry of time to clock notation converters used to localise ToClockNotation methods
Expand Down Expand Up @@ -132,6 +151,17 @@ internal static IDateToOrdinalWordConverter DateToOrdinalWordsConverter
}
}

/// <summary>
/// The converter of date to Words to be used
/// </summary>
internal static IDateToWordConverter DateToWordsConverter
{
get
{
return DateToWordsConverters.ResolveForUiCulture();
}
}

#if NET6_0_OR_GREATER
/// <summary>
/// The ordinalizer to be used
Expand All @@ -151,6 +181,25 @@ internal static ITimeOnlyToClockNotationConverter TimeOnlyToClockNotationConvert
return TimeOnlyToClockNotationConverters.ResolveForUiCulture();
}
}

/// <summary>
/// The converter of date to Words to be used
/// </summary>
internal static IDateOnlyToWordConverter DateOnlyToWordsConverter
{
get
{
return DateOnlyToWordsConverters.ResolveForUiCulture();
}
}

//internal static ITimeOnlyToClockNotationConverter TimeOnlyToClockNotationConverter
//{
// get
// {
// return TimeOnlyToClockNotationConverters.ResolveForUiCulture();
// }
//}
#endif

private static IDateTimeHumanizeStrategy _dateTimeHumanizeStrategy = new DefaultDateTimeHumanizeStrategy();
Expand Down
15 changes: 15 additions & 0 deletions 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<IDateOnlyToWordConverter>
{
public DateOnlyToWordsConverterRegistry() : base(new DefaultDateOnlyToWordConverter())
{
Register("en-US", new UsDateOnlyToWordsConverter());
Register("es", new EsDateOnlyToWordsConverter());
}
}
}
#endif
13 changes: 13 additions & 0 deletions src/Humanizer/Configuration/DateToWordsConverterRegistry.cs
@@ -0,0 +1,13 @@
using Humanizer.Localisation.DateToWords;

namespace Humanizer.Configuration
{
internal class DateToWordsConverterRegistry : LocaliserRegistry<IDateToWordConverter>
{
public DateToWordsConverterRegistry() : base(new DefaultDateToWordConverter())
{
Register("en-US", new UsDateToWordsConverter());
Register("es", new EsDateToWordsConverter());
}
}
}
53 changes: 53 additions & 0 deletions src/Humanizer/DateToWordsExtensions.cs
@@ -0,0 +1,53 @@
using System;
using Humanizer.Configuration;

namespace Humanizer
{
/// <summary>
/// Humanizes DateTime into human readable sentence
/// </summary>
public static class DateToWordsExtensions
{
/// <summary>
/// Turns the provided date into words
/// </summary>
/// <param name="input">The date to be made into words</param>
/// <returns>The date in words</returns>
public static string ToWords(this DateTime input)
{
return Configurator.DateToWordsConverter.Convert(input);
}
/// <summary>
/// Turns the provided date into words
/// </summary>
/// <param name="input">The date to be made into words</param>
/// <param name="grammaticalCase">The grammatical case to use for output words</param>
/// <returns>The date in words</returns>
public static string ToWords(this DateTime input, GrammaticalCase grammaticalCase)
{
return Configurator.DateToWordsConverter.Convert(input, grammaticalCase);
}

#if NET6_0_OR_GREATER
/// <summary>
/// Turns the provided date into words
/// </summary>
/// <param name="input">The date to be made into words</param>
/// <returns>The date in words</returns>
public static string ToWords(this DateOnly input)
{
return Configurator.DateOnlyToWordsConverter.Convert(input);
}
/// <summary>
/// Turns the provided date into words
/// </summary>
/// <param name="input">The date to be made into words</param>
/// <param name="grammaticalCase">The grammatical case to use for output words</param>
/// <returns>The date in words</returns>
public static string ToWords(this DateOnly input, GrammaticalCase grammaticalCase)
{
return Configurator.DateOnlyToWordsConverter.Convert(input, grammaticalCase);
}
#endif
}
}
@@ -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
@@ -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);
}

}
}
@@ -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
12 changes: 12 additions & 0 deletions 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();
}
}
}