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