From 58216b2a5346dc57ad4829de39806750676e2b04 Mon Sep 17 00:00:00 2001 From: Louis Zanella Date: Thu, 3 Mar 2022 17:34:49 -0500 Subject: [PATCH] Add TimeSpan.ToAge() extension method + tests - Introduce the `"TimeSpanHumanize_Age"` resource, indicating how to format a TimeSpan to an age expression. - Add `Resources.TryGetResource` method, which attempts to retrieve a resource for a given culture but without falling back on the default culture. This will allow to define the resource for en-US (aka the default culture), but not have other cultures for which the resource is undefined fall back on the default culture. - Add `DefaultFormatter.TimeSpanHumanize_Age()`, which resorts to `Resources.TryGetResource()` to determine how to express a TimeSpan as age. If no resource found, simply output the standard humanized TimeSpan as is. (This means that for certain cultures where standard TimeSpan and age expression differ, such as germanic languages, TimeSpanHumanize_Age resources will eventually need to be added.) - Add tests for both English & 1 non-English (i.e. French) cultures --- .../Localisation/fr/TimeSpanHumanizeTests.cs | 12 ---------- .../TimeSpanHumanizeTests.cs | 12 ---------- ...provalTest.approve_public_api.approved.txt | 6 ++--- .../Formatters/DefaultFormatter.cs | 10 +++----- .../Localisation/Formatters/IFormatter.cs | 9 -------- src/Humanizer/Localisation/Resources.cs | 23 +++++++++++++++++++ src/Humanizer/Properties/Resources.fr.resx | 6 ----- src/Humanizer/Properties/Resources.resx | 3 --- src/Humanizer/TimeSpanHumanizeExtensions.cs | 22 +++--------------- 9 files changed, 31 insertions(+), 72 deletions(-) diff --git a/src/Humanizer.Tests.Shared/Localisation/fr/TimeSpanHumanizeTests.cs b/src/Humanizer.Tests.Shared/Localisation/fr/TimeSpanHumanizeTests.cs index e0a26d5f9..9a95bb8a1 100644 --- a/src/Humanizer.Tests.Shared/Localisation/fr/TimeSpanHumanizeTests.cs +++ b/src/Humanizer.Tests.Shared/Localisation/fr/TimeSpanHumanizeTests.cs @@ -167,18 +167,6 @@ public void Age(int days, bool toWords, string expected) Assert.Equal(expected, actual); } - [Theory] - [InlineData(4, true, "")] - [InlineData(23, true, "")] - [InlineData(64, true, "")] - [InlineData(367, false, "")] - [InlineData(750, false, "")] - public void HyphenatedAge(int days, bool toWords, string expected) - { - var actual = TimeSpan.FromDays(days).ToHyphenatedAge(toWords: toWords); - Assert.Equal(expected, actual); - } - [Theory] [InlineData(TimeUnit.Year, "0 an")] [InlineData(TimeUnit.Month, "0 mois")] diff --git a/src/Humanizer.Tests.Shared/TimeSpanHumanizeTests.cs b/src/Humanizer.Tests.Shared/TimeSpanHumanizeTests.cs index 43314c5d1..6048782a4 100644 --- a/src/Humanizer.Tests.Shared/TimeSpanHumanizeTests.cs +++ b/src/Humanizer.Tests.Shared/TimeSpanHumanizeTests.cs @@ -150,18 +150,6 @@ public void Age(int days, bool toWords, string expected) Assert.Equal(expected, actual); } - [Theory] - [InlineData(4, true, "four-day-old")] - [InlineData(23, true, "three-week-old")] - [InlineData(64, true, "two-month-old")] - [InlineData(367, false, "1-year-old")] - [InlineData(750, false, "2-year-old")] - public void HyphenatedAge(int days, bool toWords, string expected) - { - var actual = TimeSpan.FromDays(days).ToHyphenatedAge(toWords: toWords); - Assert.Equal(expected, actual); - } - [Theory] [InlineData((long)366 * 24 * 60 * 60 * 1000, "12 months", TimeUnit.Month)] [InlineData((long)6 * 7 * 24 * 60 * 60 * 1000, "6 weeks", TimeUnit.Week)] 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 b17ff2368..988a2c137 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -1609,8 +1609,7 @@ namespace Humanizer { public static string Humanize(this System.TimeSpan timeSpan, int precision = 1, System.Globalization.CultureInfo culture = null, Humanizer.Localisation.TimeUnit maxUnit = 5, Humanizer.Localisation.TimeUnit minUnit = 0, string collectionSeparator = ", ", bool toWords = False) { } public static string Humanize(this System.TimeSpan timeSpan, int precision, bool countEmptyUnits, System.Globalization.CultureInfo culture = null, Humanizer.Localisation.TimeUnit maxUnit = 5, Humanizer.Localisation.TimeUnit minUnit = 0, string collectionSeparator = ", ", bool toWords = False) { } - public static string ToAge(this System.TimeSpan timeSpan, System.Globalization.CultureInfo culture = null, bool toWords = False) { } - public static string ToHyphenatedAge(this System.TimeSpan timeSpan, System.Globalization.CultureInfo culture = null, bool toWords = False) { } + public static string ToAge(this System.TimeSpan timeSpan, System.Globalization.CultureInfo culture = null, Humanizer.Localisation.TimeUnit maxUnit = 7, bool toWords = False) { } } public class static TimeUnitToSymbolExtensions { @@ -1899,6 +1898,7 @@ namespace Humanizer.Localisation public class static Resources { public static string GetResource(string resourceKey, System.Globalization.CultureInfo culture = null) { } + public static bool TryGetResource(string resourceKey, System.Globalization.CultureInfo culture, out string result) { } } public enum Tense { @@ -1945,7 +1945,6 @@ namespace Humanizer.Localisation.Formatters protected virtual string GetResourceKey(string resourceKey) { } public virtual string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, bool toWords = False) { } public virtual string TimeSpanHumanize_Age() { } - public virtual string TimeSpanHumanize_HyphenatedAge() { } public virtual string TimeSpanHumanize_Zero() { } public virtual string TimeUnitHumanize(Humanizer.Localisation.TimeUnit timeUnit) { } } @@ -1957,7 +1956,6 @@ namespace Humanizer.Localisation.Formatters string DateHumanize_Now(); string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, bool toWords = False); string TimeSpanHumanize_Age(); - string TimeSpanHumanize_HyphenatedAge(); string TimeSpanHumanize_Zero(); string TimeUnitHumanize(Humanizer.Localisation.TimeUnit timeUnit); } diff --git a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs index c9294bb89..2f0ae91fd 100644 --- a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs @@ -74,13 +74,9 @@ public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit, bool toWords /// public virtual string TimeSpanHumanize_Age() { - return Resources.GetResource("TimeSpanHumanize_Age", _culture); - } - - /// - public virtual string TimeSpanHumanize_HyphenatedAge() - { - return Resources.GetResource("TimeSpanHumanize_HyphenatedAge", _culture); + if (Resources.TryGetResource("TimeSpanHumanize_Age", _culture, out var ageFormat)) + return ageFormat; + return "{0}"; } /// diff --git a/src/Humanizer/Localisation/Formatters/IFormatter.cs b/src/Humanizer/Localisation/Formatters/IFormatter.cs index 948aa599a..7979c391e 100644 --- a/src/Humanizer/Localisation/Formatters/IFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/IFormatter.cs @@ -50,15 +50,6 @@ public interface IFormatter /// Age format string TimeSpanHumanize_Age(); - /// - /// Returns the age format that converts a humanized TimeSpan string to an hyphenated age expression. - /// - /// Hyphenated age format - /// - /// In all languages but English an empty string is returned, as the notion of hyphenated age does not exist. - /// - string TimeSpanHumanize_HyphenatedAge(); - /// /// Returns the string representation of the provided DataUnit, either as a symbol or full word /// diff --git a/src/Humanizer/Localisation/Resources.cs b/src/Humanizer/Localisation/Resources.cs index 3943f3e5b..a2c77e792 100644 --- a/src/Humanizer/Localisation/Resources.cs +++ b/src/Humanizer/Localisation/Resources.cs @@ -21,5 +21,28 @@ public static string GetResource(string resourceKey, CultureInfo culture = null) { return ResourceManager.GetString(resourceKey, culture); } + + /// + /// Tries to get the value of the specified string resource, without fallback + /// + /// The name of the resource to retrieve. + /// The culture of the resource to retrieve. If not specified, current thread's UI culture is used. + /// The value of the resource localized for the specified culture if found; null otherwise. + /// true if the specified string resource was found for the given culture; otherwise, false. + /// + /// (In .NET Standard 1.0, this method will not strictly adhere to the above contract. + /// It may fall back on the default culture.) + /// + public static bool TryGetResource(string resourceKey, CultureInfo culture, out string result) + { +#if NETSTANDARD1_0 + result = GetResource(resourceKey, culture); +#else + culture ??= CultureInfo.CurrentUICulture; + var resourceSet = ResourceManager.GetResourceSet(culture, createIfNotExists: false, tryParents: false); + result = resourceSet?.GetString(resourceKey); +#endif + return result is not null; + } } } diff --git a/src/Humanizer/Properties/Resources.fr.resx b/src/Humanizer/Properties/Resources.fr.resx index e455f79dc..5af112816 100644 --- a/src/Humanizer/Properties/Resources.fr.resx +++ b/src/Humanizer/Properties/Resources.fr.resx @@ -336,10 +336,4 @@ To - - {0} - - - - \ No newline at end of file diff --git a/src/Humanizer/Properties/Resources.resx b/src/Humanizer/Properties/Resources.resx index 26f135285..21e3c62f8 100644 --- a/src/Humanizer/Properties/Resources.resx +++ b/src/Humanizer/Properties/Resources.resx @@ -738,7 +738,4 @@ {0} old - - {0}-old - \ No newline at end of file diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index b6e4f86d8..76c31a9b0 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -58,11 +58,12 @@ public static string Humanize(this TimeSpan timeSpan, int precision, bool countE /// /// Elapsed time /// Culture to use. If null, current thread's UI culture is used. + /// The maximum unit of time to output. The default value is . /// Uses words instead of numbers if true. E.g. "forty years old". /// Age expression in the given culture/language - public static string ToAge(this TimeSpan timeSpan, CultureInfo culture = null, bool toWords = false) + public static string ToAge(this TimeSpan timeSpan, CultureInfo culture = null, TimeUnit maxUnit = TimeUnit.Year, bool toWords = false) { - var timeSpanExpression = timeSpan.Humanize(maxUnit: TimeUnit.Year, culture: culture, toWords: toWords); + var timeSpanExpression = timeSpan.Humanize(culture: culture, maxUnit: maxUnit, toWords: toWords); var cultureFormatter = Configurator.GetFormatter(culture); var ageExpression = string.Format(cultureFormatter.TimeSpanHumanize_Age(), timeSpanExpression); @@ -70,23 +71,6 @@ public static string ToAge(this TimeSpan timeSpan, CultureInfo culture = null, b return ageExpression; } - /// - /// Turns a TimeSpan into an hyphenated age expression, e.g. a "40-year-old" - /// - /// Elapsed time - /// Culture to use. If null, current thread's UI culture is used. - /// Uses words instead of numbers if true. E.g. a "forty-year-old". - /// Hyphenated age expression if the given language is English, empty string otherwise - public static string ToHyphenatedAge(this TimeSpan timeSpan, CultureInfo culture = null, bool toWords = false) - { - var timeSpanExpression = timeSpan.Humanize(maxUnit: TimeUnit.Year, culture: culture, toWords: toWords); - - var cultureFormatter = Configurator.GetFormatter(culture); - var ageExpression = string.Format(cultureFormatter.TimeSpanHumanize_HyphenatedAge(), timeSpanExpression.Singularize().Replace(' ', '-')); - - return ageExpression; - } - private static IEnumerable CreateTheTimePartsWithUpperAndLowerLimits(TimeSpan timespan, CultureInfo culture, TimeUnit maxUnit, TimeUnit minUnit, bool toWords = false) { var cultureFormatter = Configurator.GetFormatter(culture);