diff --git a/src/Humanizer.All.sln.DotSettings b/src/Humanizer.All.sln.DotSettings
new file mode 100644
index 000000000..02a5f0951
--- /dev/null
+++ b/src/Humanizer.All.sln.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/src/Humanizer.Tests.Shared/Localisation/de/TimeToClockNotationTests.cs b/src/Humanizer.Tests.Shared/Localisation/de/TimeToClockNotationTests.cs
new file mode 100644
index 000000000..9c2b5bc97
--- /dev/null
+++ b/src/Humanizer.Tests.Shared/Localisation/de/TimeToClockNotationTests.cs
@@ -0,0 +1,85 @@
+#if NET6_0_OR_GREATER
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+using Xunit;
+
+namespace Humanizer.Tests.Localisation.de;
+
+[UseCulture("de-DE")]
+[SuppressMessage("ReSharper", "StringLiteralTypo")]
+public static class TimeToClockNotationTests
+{
+ #region [InlineData(0, 0, "")]
+
+ [Theory]
+ [InlineData(0, 0, "Mitternacht")]
+ [InlineData(0, 7, "12 Uhr 7 nachts")]
+ [InlineData(1, 11, "1 Uhr 11 nachts")]
+ [InlineData(4, 0, "4 Uhr nachts")]
+ [InlineData(5, 1, "5 Uhr 1 nachts")]
+ [InlineData(6, 0, "6 Uhr morgens")]
+ [InlineData(6, 5, "fünf nach 6 morgens")]
+ [InlineData(7, 10, "zehn nach 7 morgens")]
+ [InlineData(8, 15, "Viertel nach 8 morgens")]
+ [InlineData(9, 20, "zwanzig nach 9 morgens")]
+ [InlineData(10, 25, "fünf vor halb 11 morgens")]
+ [InlineData(11, 30, "halb 12 morgens")]
+ [InlineData(12, 00, "Mittag")]
+ [InlineData(12, 38, "12 Uhr 38 nachmittags")]
+ [InlineData(12, 35, "fünf nach halb 1 nachmittags")]
+ [InlineData(15, 40, "zwanzig vor 4 nachmittags")]
+ [InlineData(17, 45, "Viertel vor 6 nachmittags")]
+ [InlineData(19, 50, "zehn vor 8 abends")]
+ [InlineData(21, 0, "9 Uhr abends")]
+ [InlineData(21, 55, "fünf vor 10 abends")]
+ [InlineData(22, 59, "10 Uhr 59 abends")]
+ [InlineData(23, 43, "11 Uhr 43 abends")]
+
+ #endregion [InlineData(0, 0, "")]
+
+ public static void ConvertToClockNotationTimeOnlyStringDe(int hours, int minutes, string expectedResult)
+ {
+ var actualResult = new TimeOnly(hours, minutes).ToClockNotation();
+ Assert.Equal(expectedResult, actualResult);
+ }
+
+
+ #region [InlineData(0, 0, "")]
+
+ [Theory]
+ [InlineData(0, 0, "Mitternacht")]
+ [InlineData(0, 7, "fünf nach 12 nachts")]
+ [InlineData(1, 11, "zehn nach 1 nachts")]
+ [InlineData(4, 0, "4 Uhr nachts")]
+ [InlineData(5, 1, "5 Uhr nachts")]
+ [InlineData(6, 0, "6 Uhr morgens")]
+ [InlineData(6, 5, "fünf nach 6 morgens")]
+ [InlineData(7, 10, "zehn nach 7 morgens")]
+ [InlineData(8, 15, "Viertel nach 8 morgens")]
+ [InlineData(9, 20, "zwanzig nach 9 morgens")]
+ [InlineData(10, 25, "fünf vor halb 11 morgens")]
+ [InlineData(11, 30, "halb 12 morgens")]
+ [InlineData(12, 00, "Mittag")]
+ [InlineData(12, 38, "zwanzig vor 1 nachmittags")]
+ [InlineData(12, 35, "fünf nach halb 1 nachmittags")]
+ [InlineData(15, 40, "zwanzig vor 4 nachmittags")]
+ [InlineData(17, 45, "Viertel vor 6 nachmittags")]
+ [InlineData(19, 50, "zehn vor 8 abends")]
+ [InlineData(21, 0, "9 Uhr abends")]
+ [InlineData(21, 55, "fünf vor 10 abends")]
+ [InlineData(22, 59, "11 Uhr abends")]
+ [InlineData(23, 43, "Viertel vor 12 abends")]
+ [InlineData(23, 58, "Mitternacht")]
+
+ #endregion [InlineData(0, 0, "")]
+
+ public static void ConvertToRoundedClockNotationTimeOnlyStringPtBr(int hours, int minutes, string expectedResult)
+ {
+ var actualResult = new TimeOnly(hours, minutes).ToClockNotation(ClockNotationRounding.NearestFiveMinutes);
+ Assert.Equal(expectedResult, actualResult);
+ }
+}
+
+#endif
diff --git a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt
index ec44c3fdc..aa9b2916b 100644
--- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt
+++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt
@@ -1949,9 +1949,143 @@ namespace Humanizer
public string? Singularize(string? word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) { }
}
public enum WordForm
- {
+{
Normal = 0,
Abbreviation = 1,
Eifeler = 2,
}
+}
+namespace Humanizer.Localisation
+{
+ public enum DataUnit
+ {
+ Bit = 0,
+ Byte = 1,
+ Kilobyte = 2,
+ Megabyte = 3,
+ Gigabyte = 4,
+ Terabyte = 5,
+ }
+ public class ResourceKeys
+ {
+ public ResourceKeys() { }
+ public class static DateHumanize
+ {
+ public const string Never = "DateHumanize_Never";
+ public const string Now = "DateHumanize_Now";
+ public static string GetResourceKey(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int count = 1) { }
+ }
+ public class static TimeSpanHumanize
+ {
+ public static string GetResourceKey(Humanizer.Localisation.TimeUnit unit, int count = 1, bool toWords = False) { }
+ }
+ public class static TimeUnitSymbol
+ {
+ public static string GetResourceKey(Humanizer.Localisation.TimeUnit unit) { }
+ }
+ }
+ public class static Resources
+ {
+ public static string GetResource(string resourceKey, System.Globalization.CultureInfo culture = null) { }
+ }
+ public enum Tense
+ {
+ Future = 0,
+ Past = 1,
+ }
+ public enum TimeUnit
+ {
+ Millisecond = 0,
+ Second = 1,
+ Minute = 2,
+ Hour = 3,
+ Day = 4,
+ Week = 5,
+ Month = 6,
+ Year = 7,
+ }
+}
+namespace Humanizer.Localisation.DateToOrdinalWords
+{
+ public interface IDateOnlyToOrdinalWordConverter
+ {
+ string Convert(System.DateOnly date);
+ string Convert(System.DateOnly date, Humanizer.GrammaticalCase grammaticalCase);
+ }
+ public interface IDateToOrdinalWordConverter
+ {
+ string Convert(System.DateTime date);
+ string Convert(System.DateTime date, Humanizer.GrammaticalCase grammaticalCase);
+ }
+}
+namespace Humanizer.Localisation.Formatters
+{
+ public class DefaultFormatter : Humanizer.Localisation.Formatters.IFormatter
+ {
+ public DefaultFormatter(string localeCode) { }
+ public virtual string DataUnitHumanize(Humanizer.Localisation.DataUnit dataUnit, double count, bool toSymbol = True) { }
+ public virtual string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit) { }
+ public virtual string DateHumanize_Never() { }
+ public virtual string DateHumanize_Now() { }
+ protected virtual string Format(string resourceKey) { }
+ protected virtual string Format(string resourceKey, int number, bool toWords = False) { }
+ protected virtual string GetResourceKey(string resourceKey, int number) { }
+ protected virtual string GetResourceKey(string resourceKey) { }
+ public virtual string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, bool toWords = False) { }
+ public virtual string TimeSpanHumanize_Zero() { }
+ public virtual string TimeUnitHumanize(Humanizer.Localisation.TimeUnit timeUnit) { }
+ }
+ public interface IFormatter
+ {
+ string DataUnitHumanize(Humanizer.Localisation.DataUnit dataUnit, double count, bool toSymbol = True);
+ string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit);
+ string DateHumanize_Never();
+ string DateHumanize_Now();
+ string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, bool toWords = False);
+ string TimeSpanHumanize_Zero();
+ string TimeUnitHumanize(Humanizer.Localisation.TimeUnit timeUnit);
+ }
+}
+namespace Humanizer.Localisation.NumberToWords
+{
+ public interface INumberToWordsConverter
+ {
+ string Convert(long number);
+ string Convert(long number, Humanizer.WordForm wordForm);
+ string Convert(long number, bool addAnd);
+ string Convert(long number, bool addAnd, Humanizer.WordForm wordForm);
+ string Convert(long number, Humanizer.GrammaticalGender gender, bool addAnd = True);
+ string Convert(long number, Humanizer.WordForm wordForm, Humanizer.GrammaticalGender gender, bool addAnd = True);
+ string ConvertToOrdinal(int number);
+ string ConvertToOrdinal(int number, Humanizer.WordForm wordForm);
+ string ConvertToOrdinal(int number, Humanizer.GrammaticalGender gender);
+ string ConvertToOrdinal(int number, Humanizer.GrammaticalGender gender, Humanizer.WordForm wordForm);
+ string ConvertToTuple(int number);
+ }
+}
+namespace Humanizer.Localisation.Ordinalizers
+{
+ public interface IOrdinalizer
+ {
+ string Convert(int number, string numberString);
+ string Convert(int number, string numberString, Humanizer.WordForm wordForm);
+ string Convert(int number, string numberString, Humanizer.GrammaticalGender gender);
+ string Convert(int number, string numberString, Humanizer.GrammaticalGender gender, Humanizer.WordForm wordForm);
+ }
+}
+namespace Humanizer.Localisation.TimeToClockNotation
+{
+ public class static German
+ {
+ public const string MidDay = "Mittag";
+ public const string MidNight = "Mitternacht";
+ public static readonly System.Collections.Generic.IReadOnlyList QuarterDay;
+ public static string AsClockDe(this System.TimeOnly time, Humanizer.ClockNotationRounding round, bool asWords = False) { }
+ public static string GetDayQuarterGerman(this System.TimeOnly time) { }
+ public static string GetDayQuarterGermanGenitive(this System.TimeOnly time) { }
+ }
+ public interface ITimeOnlyToClockNotationConverter
+ {
+ string Convert(System.TimeOnly time, Humanizer.ClockNotationRounding roundToNearestFive);
+ }
}
\ No newline at end of file
diff --git a/src/Humanizer/Configuration/TimeOnlyToClockNotationConvertersRegistry.cs b/src/Humanizer/Configuration/TimeOnlyToClockNotationConvertersRegistry.cs
index bdc9c53b6..ad6d2f0d7 100644
--- a/src/Humanizer/Configuration/TimeOnlyToClockNotationConvertersRegistry.cs
+++ b/src/Humanizer/Configuration/TimeOnlyToClockNotationConvertersRegistry.cs
@@ -1,16 +1,17 @@
-#if NET6_0_OR_GREATER
+#if NET6_0_OR_GREATER
namespace Humanizer;
class TimeOnlyToClockNotationConvertersRegistry : LocaliserRegistry
{
- public TimeOnlyToClockNotationConvertersRegistry() : base(new DefaultTimeOnlyToClockNotationConverter())
- {
- Register("pt-BR", new BrazilianPortugueseTimeOnlyToClockNotationConverter());
- Register("fr", new FrTimeOnlyToClockNotationConverter());
- Register("es", new EsTimeOnlyToClockNotationConverter());
- Register("lb", new LbTimeOnlyToClockNotationConverter());
+ public TimeOnlyToClockNotationConvertersRegistry() : base(new DefaultTimeOnlyToClockNotationConverter())
+ {
+ Register("pt-BR", new BrazilianPortugueseTimeOnlyToClockNotationConverter());
+ Register("fr", new FrTimeOnlyToClockNotationConverter());
+ Register("es", new EsTimeOnlyToClockNotationConverter());
+ Register("lb", new LbTimeOnlyToClockNotationConverter());
+ Register("de", new DeTimeOnlyToClockNotationConverter());
+ }
}
-}
#endif
diff --git a/src/Humanizer/Localisation/Ordinalizers/EnglishOrdinalizer.cs b/src/Humanizer/Localisation/Ordinalizers/EnglishOrdinalizer.cs
index 2dd0f5dbb..55a83ca1a 100644
--- a/src/Humanizer/Localisation/Ordinalizers/EnglishOrdinalizer.cs
+++ b/src/Humanizer/Localisation/Ordinalizers/EnglishOrdinalizer.cs
@@ -2,28 +2,21 @@
class EnglishOrdinalizer : DefaultOrdinalizer
{
- public override string Convert(int number, string numberString)
- {
- var nMod100 = number % 100;
-
- if (nMod100 is >= 11 and <= 20)
- {
- return numberString + "th";
- }
-
- switch (number % 10)
+ public override string Convert(int number, string numberString)
{
- case 1:
- return numberString + "st";
+ var nMod100 = number % 100;
- case 2:
- return numberString + "nd";
-
- case 3:
- return numberString + "rd";
-
- default:
+ if (nMod100 is >= 11 and <= 20)
+ {
return numberString + "th";
+ }
+
+ return (number % 10) switch
+ {
+ 1 => numberString + "st",
+ 2 => numberString + "nd",
+ 3 => numberString + "rd",
+ _ => numberString + "th"
+ };
}
}
-}
\ No newline at end of file
diff --git a/src/Humanizer/Localisation/TimeToClockNotation/DeTimeOnlyToClockNotationConverter.cs b/src/Humanizer/Localisation/TimeToClockNotation/DeTimeOnlyToClockNotationConverter.cs
new file mode 100644
index 000000000..c53830486
--- /dev/null
+++ b/src/Humanizer/Localisation/TimeToClockNotation/DeTimeOnlyToClockNotationConverter.cs
@@ -0,0 +1,31 @@
+#if NET6_0_OR_GREATER
+
+using System;
+
+namespace Humanizer;
+
+internal class DeTimeOnlyToClockNotationConverter : ITimeOnlyToClockNotationConverter
+{
+ /// Switch to output Digits as Words
+ public bool AsWords;
+
+ /// Used to pre-pend, append or omit the Quarter of the Day
+ public bool? PrePendQuarter = false;
+
+ public string Convert(TimeOnly time, ClockNotationRounding roundToNearestFive)
+ {
+ var ret = time.AsClockDe(roundToNearestFive, AsWords);
+ if (!PrePendQuarter.HasValue
+ || ret == German.MidNight
+ || ret == German.MidDay)
+ return ret;
+
+ return PrePendQuarter.Value switch
+ {
+ false => ret + ' ' + time.GetDayQuarterGermanGenitive(),
+ true => time.GetDayQuarterGermanGenitive() + ' ' + ret
+ };
+ }
+}
+
+#endif
diff --git a/src/Humanizer/Localisation/TimeToClockNotation/German.cs b/src/Humanizer/Localisation/TimeToClockNotation/German.cs
new file mode 100644
index 000000000..c65ef910f
--- /dev/null
+++ b/src/Humanizer/Localisation/TimeToClockNotation/German.cs
@@ -0,0 +1,67 @@
+#if NET6_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Humanizer;
+
+public static class German
+{
+ public const string MidNight = "Mitternacht";
+ public const string MidDay = "Mittag";
+
+ public static readonly IReadOnlyList QuarterDay = new[] {"Nacht", "Morgen", "Nachmittag", "Abend"};
+
+ public static string GetDayQuarterGerman(this TimeOnly time) => QuarterDay[time.Hour / 6];
+ public static string GetDayQuarterGermanGenitive(this TimeOnly time) => GetDayQuarterGerman(time).ToLower() + "s";
+
+ /// Use 12 Hours but avoid 0
+ private static int NormalizeHour(TimeOnly time) => time.Hour % 12 != 0 ? (time.Hour % 12) : 12;
+
+ [SuppressMessage("ReSharper", "StringLiteralTypo")]
+ public static string AsClockDe(this TimeOnly time, ClockNotationRounding round, bool asWords = false)
+ {
+ if (round == ClockNotationRounding.NearestFiveMinutes)
+ {
+ var ticks = 5 * TimeSpan.TicksPerMinute;
+ var quotient = (time.Ticks + ticks / 2) / ticks;
+ var total = quotient * ticks;
+ if (total >= TimeSpan.TicksPerDay)
+ {
+ total -= TimeSpan.TicksPerDay;
+ }
+ time = new TimeOnly(total);
+ }
+ switch (time)
+ {
+ case { Hour: 0, Minute: 0 }: return MidNight;
+ case { Hour: 12, Minute: 0 }: return MidDay;
+ }
+
+ var addHour = time.AddHours(1);
+ var hours = NormalizeHour(time);
+ var hour = asWords ? hours.ToWords() : hours.ToString();
+ var nextHours = NormalizeHour(addHour);
+ var nextHour = asWords ? nextHours.ToWords() : nextHours.ToString();
+
+ return time.Minute switch
+ {
+ 00 => $"{hour} Uhr",
+ 05 => $"fünf nach {hour}",
+ 10 => $"zehn nach {hour}",
+ 15 => $"Viertel nach {hour}",
+ 20 => $"zwanzig nach {hour}",
+ 25 => $"fünf vor halb {nextHour}",
+ 30 => $"halb {nextHour}",
+ 35 => $"fünf nach halb {nextHour}",
+ 40 => $"zwanzig vor {nextHour}",
+ 45 => $"Viertel vor {nextHour}",
+ 50 => $"zehn vor {nextHour}",
+ 55 => $"fünf vor {nextHour}",
+ 60 => $"{nextHour} Uhr",
+ _ => $"{hour} Uhr {time.Minute}" //.AsWords()
+ };
+ }
+
+}
+#endif