From 530b2f51af2d7e08fa302cff1bbaa064c8fc4d56 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Wed, 28 May 2014 22:37:46 +0300 Subject: [PATCH 01/11] Added option Culture parameter to DateTime.Humanize --- ...provalTest.approve_public_api.approved.txt | 19 +++++---- src/Humanizer.Tests/DateHumanize.cs | 42 +++++++++++++------ .../Localisation/nl/DateHumanizeTests.cs | 10 ++--- src/Humanizer/Configuration/Configurator.cs | 11 +++-- .../Configuration/LocaliserRegistry.cs | 13 +++++- src/Humanizer/DateHumanizeExtensions.cs | 6 ++- .../DefaultDateTimeHumanizeStrategy.cs | 30 +++++++------ .../IDateTimeHumanizeStrategy.cs | 3 +- .../PrecisionDateTimeHumanizeStrategy.cs | 19 +++++---- .../Formatters/DefaultFormatter.cs | 30 +++++++------ .../Localisation/Formatters/IFormatter.cs | 10 +++-- src/Humanizer/Localisation/Resources.cs | 8 ++-- src/Humanizer/TimeSpanHumanizeExtensions.cs | 2 +- 13 files changed, 126 insertions(+), 77 deletions(-) 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 f1381d093..d7b6055ab 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -87,29 +87,30 @@ public class LocaliserRegistry`1 public void Register(string localeCode) { } public void Register(System.Func<> localiserFactory, string localeCode) { } public void RegisterDefault(TLocaliser defaultLocaliser) { } + public TLocaliser ResolveForCulture(System.Globalization.CultureInfo culture) { } public TLocaliser ResolveForUiCulture() { } } public class DateHumanizeExtensions { - public string Humanize(System.DateTime input, bool utcDate, System.Nullable dateToCompareAgainst) { } + public string Humanize(System.DateTime input, bool utcDate, System.Nullable dateToCompareAgainst, System.Globalization.CultureInfo culture) { } } public class DefaultDateTimeHumanizeStrategy { public DefaultDateTimeHumanizeStrategy() { } - public string Humanize(System.DateTime input, System.DateTime comparisonBase) { } + public string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture) { } } public interface IDateTimeHumanizeStrategy { - string Humanize(System.DateTime input, System.DateTime comparisonBase); + string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture); } public class PrecisionDateTimeHumanizeStrategy { public PrecisionDateTimeHumanizeStrategy(double precision) { } - public string Humanize(System.DateTime input, System.DateTime comparisonBase) { } + public string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture) { } } public class EnumDehumanizeExtensions @@ -215,16 +216,16 @@ public interface ICollectionFormatter public class DefaultFormatter { public DefaultFormatter() { } - public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit) { } - public string DateHumanize_Now() { } + public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture) { } + public string DateHumanize_Now(System.Globalization.CultureInfo culture) { } public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit) { } public string TimeSpanHumanize_Zero() { } } public interface IFormatter { - string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit); - string DateHumanize_Now(); + string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture); + string DateHumanize_Now(System.Globalization.CultureInfo culture); string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit); string TimeSpanHumanize_Zero(); } @@ -250,7 +251,7 @@ public class ResourceKeys public class Resources { - public string GetResource(string resourceKey) { } + public string GetResource(string resourceKey, System.Globalization.CultureInfo culture) { } } public enum Tense diff --git a/src/Humanizer.Tests/DateHumanize.cs b/src/Humanizer.Tests/DateHumanize.cs index e498a2904..42d312c99 100644 --- a/src/Humanizer.Tests/DateHumanize.cs +++ b/src/Humanizer.Tests/DateHumanize.cs @@ -1,4 +1,6 @@ using System; +using System.Globalization; +using System.Threading; using Humanizer.Configuration; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation; @@ -8,26 +10,33 @@ namespace Humanizer.Tests { public class DateHumanize { - static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) + private static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) { - var utcNow = DateTime.UtcNow; - var localNow = DateTime.Now; + CheckWithExplicitAndImplicitCulture(culture => + { + var utcNow = DateTime.UtcNow; + var localNow = DateTime.Now; - // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); - Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(utcDate: false, dateToCompareAgainst: localNow)); + // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(true, utcNow, culture)); + Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(false, localNow, culture)); + }); } - static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) + private static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) { - var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); - var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); + CheckWithExplicitAndImplicitCulture(culture => + { + var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); + var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); - Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now)); + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(true, utcNow, culture)); + Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now, culture)); + }); } - public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null) + public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, + double? precision = null) { if (precision.HasValue) Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision.Value); @@ -68,5 +77,14 @@ public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Te VerifyWithCurrentDate(expectedString, deltaFromNow); VerifyWithDateInjection(expectedString, deltaFromNow); } + + private static void CheckWithExplicitAndImplicitCulture(Action action) + { + action(null); + + CultureInfo culture = Thread.CurrentThread.CurrentUICulture; + using (new AmbientCulture(culture.TwoLetterISOLanguageName == "da" ? "tr" : "da")) + action(culture); + } } } \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/nl/DateHumanizeTests.cs b/src/Humanizer.Tests/Localisation/nl/DateHumanizeTests.cs index d598bbebc..85e736950 100644 --- a/src/Humanizer.Tests/Localisation/nl/DateHumanizeTests.cs +++ b/src/Humanizer.Tests/Localisation/nl/DateHumanizeTests.cs @@ -14,7 +14,7 @@ public DateHumanizeTests() : base("nl-NL") { } [InlineData(-1, "gisteren")] public void DaysAgo(int days, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddDays(days).Humanize()); + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past); } [Theory] @@ -22,7 +22,7 @@ public void DaysAgo(int days, string expected) [InlineData(-1, "één uur geleden")] public void HoursAgo(int hours, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddHours(hours).Humanize()); + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past); } [Theory] @@ -39,7 +39,7 @@ public void MinutesAgo(int minutes, string expected) [InlineData(-1, "één maand geleden")] public void MonthsAgo(int months, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddMonths(months).Humanize()); + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past); } [Theory] @@ -47,7 +47,7 @@ public void MonthsAgo(int months, string expected) [InlineData(-1, "één seconde geleden")] public void SecondsAgo(int seconds, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddSeconds(seconds).Humanize()); + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past); } [Theory] @@ -55,7 +55,7 @@ public void SecondsAgo(int seconds, string expected) [InlineData(-1, "één jaar geleden")] public void YearsAgo(int years, string expected) { - Assert.Equal(expected, DateTime.UtcNow.AddYears(years).Humanize()); + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past); } } } diff --git a/src/Humanizer/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs index af94e3c22..581fb7f01 100644 --- a/src/Humanizer/Configuration/Configurator.cs +++ b/src/Humanizer/Configuration/Configurator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Reflection; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation.Formatters; @@ -58,16 +59,14 @@ internal static ICollectionFormatter CollectionFormatter return CollectionFormatters.ResolveForUiCulture(); } } - + /// /// The formatter to be used /// - internal static IFormatter Formatter + /// The culture to retrieve formatter for. If not specified, current thread's UI culture is used. + internal static IFormatter GetFormatter(CultureInfo culture = null) { - get - { - return Formatters.ResolveForUiCulture(); - } + return Formatters.ResolveForCulture(culture); } /// diff --git a/src/Humanizer/Configuration/LocaliserRegistry.cs b/src/Humanizer/Configuration/LocaliserRegistry.cs index 8491d4a9f..a0dab6ec0 100644 --- a/src/Humanizer/Configuration/LocaliserRegistry.cs +++ b/src/Humanizer/Configuration/LocaliserRegistry.cs @@ -23,11 +23,20 @@ public LocaliserRegistry(TLocaliser defaultLocaliser) } /// - /// Gets the localiser for the current UI culture + /// Gets the localiser for the current thread's UI culture /// public TLocaliser ResolveForUiCulture() { - var culture = CultureInfo.CurrentUICulture; + return ResolveForCulture(); + } + + /// + /// Gets the localiser for the specified culture + /// + /// The culture to retrieve localiser for. If not specified, current thread's UI culture is used. + public TLocaliser ResolveForCulture(CultureInfo culture = null) + { + culture = culture ?? CultureInfo.CurrentUICulture; Lazy factory; diff --git a/src/Humanizer/DateHumanizeExtensions.cs b/src/Humanizer/DateHumanizeExtensions.cs index 7147e3baf..70874e073 100644 --- a/src/Humanizer/DateHumanizeExtensions.cs +++ b/src/Humanizer/DateHumanizeExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Humanizer.Configuration; namespace Humanizer @@ -14,15 +15,16 @@ public static class DateHumanizeExtensions /// The date to be humanized /// Boolean value indicating whether the date is in UTC or local /// Date to compare the input against. If null, current date is used as base + /// Culture to use. If null, current thread's UI culture is used. /// distance of time in words - public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null) + public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null, CultureInfo culture = null) { var comparisonBase = dateToCompareAgainst ?? DateTime.UtcNow; if (!utcDate) comparisonBase = comparisonBase.ToLocalTime(); - return Configurator.DateTimeHumanizeStrategy.Humanize(input, comparisonBase); + return Configurator.DateTimeHumanizeStrategy.Humanize(input, comparisonBase, culture); } } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs index 284bab811..4ee8195f9 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Humanizer.Configuration; using Humanizer.Localisation; @@ -15,53 +16,56 @@ public class DefaultDateTimeHumanizeStrategy : IDateTimeHumanizeStrategy /// /// /// + /// /// - public string Humanize(DateTime input, DateTime comparisonBase) + public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture) { var tense = input > comparisonBase ? Tense.Future : Tense.Past; var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); + var formatter = Configurator.GetFormatter(culture); + if (ts.TotalMilliseconds < 500) - return Configurator.Formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, culture); if (ts.TotalSeconds < 60) - return Configurator.Formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds); + return formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds, culture); if (ts.TotalSeconds < 120) - return Configurator.Formatter.DateHumanize(TimeUnit.Minute, tense, 1); + return formatter.DateHumanize(TimeUnit.Minute, tense, 1, culture); if (ts.TotalMinutes < 60) - return Configurator.Formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes); + return formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes, culture); if (ts.TotalMinutes < 90) - return Configurator.Formatter.DateHumanize(TimeUnit.Hour, tense, 1); + return formatter.DateHumanize(TimeUnit.Hour, tense, 1, culture); if (ts.TotalHours < 24) - return Configurator.Formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours); + return formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours, culture); if (ts.TotalHours < 48) - return Configurator.Formatter.DateHumanize(TimeUnit.Day, tense, 1); + return formatter.DateHumanize(TimeUnit.Day, tense, 1, culture); if (ts.TotalDays < 28) - return Configurator.Formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, culture); if (ts.TotalDays >= 28 && ts.TotalDays < 30) { if (comparisonBase.Date.AddMonths(tense == Tense.Future ? 1 : -1) == input.Date) - return Configurator.Formatter.DateHumanize(TimeUnit.Month, tense, 1); - return Configurator.Formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); + return formatter.DateHumanize(TimeUnit.Month, tense, 1, culture); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, culture); } if (ts.TotalDays < 345) { int months = Convert.ToInt32(Math.Floor(ts.TotalDays / 29.5)); - return Configurator.Formatter.DateHumanize(TimeUnit.Month, tense, months); + return formatter.DateHumanize(TimeUnit.Month, tense, months, culture); } int years = Convert.ToInt32(Math.Floor(ts.TotalDays / 365)); if (years == 0) years = 1; - return Configurator.Formatter.DateHumanize(TimeUnit.Year, tense, years); + return formatter.DateHumanize(TimeUnit.Year, tense, years, culture); } } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs index 5aedd233b..7d663a794 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace Humanizer.DateTimeHumanizeStrategy { @@ -10,6 +11,6 @@ public interface IDateTimeHumanizeStrategy /// /// Calculates the distance of time in words between two provided dates used for DateTime.Humanize /// - string Humanize(DateTime input, DateTime comparisonBase); + string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture); } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs index 06c243b8b..cec212dd3 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Humanizer.Configuration; using Humanizer.Localisation; @@ -25,8 +26,9 @@ public PrecisionDateTimeHumanizeStrategy(double precision = .75) /// /// /// + /// /// - public string Humanize(DateTime input, DateTime comparisonBase) + public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture) { var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); var tense = input > comparisonBase ? Tense.Future : Tense.Past; @@ -59,13 +61,14 @@ public string Humanize(DateTime input, DateTime comparisonBase) } // start computing result from larger units to smaller ones - if (years > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Year, tense, years); - if (months > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Month, tense, months); - if (days > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Day, tense, days); - if (hours > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Hour, tense, hours); - if (minutes > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Minute, tense, minutes); - if (seconds > 0) return Configurator.Formatter.DateHumanize(TimeUnit.Second, tense, seconds); - return Configurator.Formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); + var formatter = Configurator.GetFormatter(culture); + if (years > 0) return formatter.DateHumanize(TimeUnit.Year, tense, years, culture); + if (months > 0) return formatter.DateHumanize(TimeUnit.Month, tense, months, culture); + if (days > 0) return formatter.DateHumanize(TimeUnit.Day, tense, days, culture); + if (hours > 0) return formatter.DateHumanize(TimeUnit.Hour, tense, hours, culture); + if (minutes > 0) return formatter.DateHumanize(TimeUnit.Minute, tense, minutes, culture); + if (seconds > 0) return formatter.DateHumanize(TimeUnit.Second, tense, seconds, culture); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, culture); } } } \ No newline at end of file diff --git a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs index 37eca6839..66ec76a77 100644 --- a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs @@ -1,4 +1,6 @@ -namespace Humanizer.Localisation.Formatters +using System.Globalization; + +namespace Humanizer.Localisation.Formatters { /// /// Default implementation of IFormatter interface. @@ -8,10 +10,11 @@ public class DefaultFormatter : IFormatter /// /// Now /// + /// /// Returns Now - public virtual string DateHumanize_Now() + public virtual string DateHumanize_Now(CultureInfo culture) { - return GetResourceForDate(TimeUnit.Millisecond, Tense.Past, 0); + return GetResourceForDate(TimeUnit.Millisecond, Tense.Past, 0, culture); } /// @@ -20,10 +23,11 @@ public virtual string DateHumanize_Now() /// /// /// + /// /// - public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit) + public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, CultureInfo culture) { - return GetResourceForDate(timeUnit, timeUnitTense, unit); + return GetResourceForDate(timeUnit, timeUnitTense, unit, culture); } /// @@ -46,26 +50,27 @@ public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit) return GetResourceForTimeSpan(timeUnit, unit); } - private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count) + private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, CultureInfo culture) { string resourceKey = ResourceKeys.DateHumanize.GetResourceKey(unit, timeUnitTense: timeUnitTense, count: count); - return count == 1 ? Format(resourceKey) : Format(resourceKey, count); + return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); } private string GetResourceForTimeSpan(TimeUnit unit, int count) { string resourceKey = ResourceKeys.TimeSpanHumanize.GetResourceKey(unit, count); - return count == 1 ? Format(resourceKey) : Format(resourceKey, count); + return count == 1 ? Format(resourceKey, null) : Format(resourceKey, count, null); } /// /// /// /// + /// /// - protected virtual string Format(string resourceKey) + protected virtual string Format(string resourceKey, CultureInfo culture) { - return Resources.GetResource(GetResourceKey(resourceKey)); + return Resources.GetResource(GetResourceKey(resourceKey), culture); } /// @@ -73,10 +78,11 @@ protected virtual string Format(string resourceKey) /// /// /// + /// /// - protected virtual string Format(string resourceKey, int number) + protected virtual string Format(string resourceKey, int number, CultureInfo culture) { - return Resources.GetResource(GetResourceKey(resourceKey, number)).FormatWith(number); + return Resources.GetResource(GetResourceKey(resourceKey, number), culture).FormatWith(number); } /// diff --git a/src/Humanizer/Localisation/Formatters/IFormatter.cs b/src/Humanizer/Localisation/Formatters/IFormatter.cs index 887209658..462efef21 100644 --- a/src/Humanizer/Localisation/Formatters/IFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/IFormatter.cs @@ -1,4 +1,6 @@ -namespace Humanizer.Localisation.Formatters +using System.Globalization; + +namespace Humanizer.Localisation.Formatters { /// /// Implement this interface if your language has complex rules around dealing with numbers. @@ -10,8 +12,9 @@ public interface IFormatter /// /// Now /// + /// /// Returns Now - string DateHumanize_Now(); + string DateHumanize_Now(CultureInfo culture); /// /// Returns the string representation of the provided DateTime @@ -19,8 +22,9 @@ public interface IFormatter /// /// /// + /// /// - string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit); + string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, CultureInfo culture); /// /// 0 seconds diff --git a/src/Humanizer/Localisation/Resources.cs b/src/Humanizer/Localisation/Resources.cs index 1e028632b..6eafd3cfd 100644 --- a/src/Humanizer/Localisation/Resources.cs +++ b/src/Humanizer/Localisation/Resources.cs @@ -1,4 +1,5 @@ -using System.Resources; +using System.Globalization; +using System.Resources; namespace Humanizer.Localisation { @@ -13,10 +14,11 @@ public static class Resources /// Returns the value of the specified string resource /// /// The name of the resource to retrieve. + /// The culture of the resource to retrieve. /// The value of the resource localized for the caller's current UI culture. - public static string GetResource(string resourceKey) + public static string GetResource(string resourceKey, CultureInfo culture = null) { - return ResourceManager.GetString(resourceKey); + return ResourceManager.GetString(resourceKey, culture); } } } diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index f51c2831c..660b3be2e 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -38,7 +38,7 @@ public static string Humanize(this TimeSpan timeSpan, int precision = 1) private static string GetTimePart(TimeSpan timespan) { - var formatter = Configurator.Formatter; + var formatter = Configurator.GetFormatter(); if (timespan.Days >= 7) return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7); From adea66c3f26cec533dadd25b7f8132fa518b9566 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 29 May 2014 15:54:18 +0300 Subject: [PATCH 02/11] Fixed formatting --- src/Humanizer.Tests/DateHumanize.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Humanizer.Tests/DateHumanize.cs b/src/Humanizer.Tests/DateHumanize.cs index 42d312c99..dda5ce615 100644 --- a/src/Humanizer.Tests/DateHumanize.cs +++ b/src/Humanizer.Tests/DateHumanize.cs @@ -35,8 +35,7 @@ private static void VerifyWithDateInjection(string expectedString, TimeSpan delt }); } - public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, - double? precision = null) + public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null) { if (precision.HasValue) Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision.Value); From 3dde23dbdf31a81bdbcd4e6e47130ee4fb235e19 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 29 May 2014 19:51:31 +0300 Subject: [PATCH 03/11] Reverted changes to DateHumanize (testing with both implicit & explicit culture) --- src/Humanizer.Tests/DateHumanize.cs | 39 ++++++++--------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/src/Humanizer.Tests/DateHumanize.cs b/src/Humanizer.Tests/DateHumanize.cs index dda5ce615..e498a2904 100644 --- a/src/Humanizer.Tests/DateHumanize.cs +++ b/src/Humanizer.Tests/DateHumanize.cs @@ -1,6 +1,4 @@ using System; -using System.Globalization; -using System.Threading; using Humanizer.Configuration; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation; @@ -10,29 +8,23 @@ namespace Humanizer.Tests { public class DateHumanize { - private static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) + static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) { - CheckWithExplicitAndImplicitCulture(culture => - { - var utcNow = DateTime.UtcNow; - var localNow = DateTime.Now; + var utcNow = DateTime.UtcNow; + var localNow = DateTime.Now; - // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(true, utcNow, culture)); - Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(false, localNow, culture)); - }); + // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); + Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(utcDate: false, dateToCompareAgainst: localNow)); } - private static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) + static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) { - CheckWithExplicitAndImplicitCulture(culture => - { - var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); - var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); + var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); + var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(true, utcNow, culture)); - Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now, culture)); - }); + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); + Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now)); } public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null) @@ -76,14 +68,5 @@ public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Te VerifyWithCurrentDate(expectedString, deltaFromNow); VerifyWithDateInjection(expectedString, deltaFromNow); } - - private static void CheckWithExplicitAndImplicitCulture(Action action) - { - action(null); - - CultureInfo culture = Thread.CurrentThread.CurrentUICulture; - using (new AmbientCulture(culture.TwoLetterISOLanguageName == "da" ? "tr" : "da")) - action(culture); - } } } \ No newline at end of file From abbc23ed12d6864750113d78abf69dd35c2e2a57 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 29 May 2014 19:56:32 +0300 Subject: [PATCH 04/11] Added a test for getting culture-specific resource with explicitly specified culture --- src/Humanizer.Tests/Localisation/ResourcesTests.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Humanizer.Tests/Localisation/ResourcesTests.cs b/src/Humanizer.Tests/Localisation/ResourcesTests.cs index 6f8897705..54f061164 100644 --- a/src/Humanizer.Tests/Localisation/ResourcesTests.cs +++ b/src/Humanizer.Tests/Localisation/ResourcesTests.cs @@ -1,4 +1,5 @@ -using Humanizer.Localisation; +using System.Globalization; +using Humanizer.Localisation; using Xunit; namespace Humanizer.Tests.Localisation @@ -6,7 +7,7 @@ namespace Humanizer.Tests.Localisation public class ResourcesTests { [Fact] - public void CanGetCultureSpecificTranslations() + public void CanGetCultureSpecificTranslationsWithImplicitCulture() { using (new AmbientCulture("ro")) { @@ -14,5 +15,12 @@ public void CanGetCultureSpecificTranslations() Assert.Equal("acum {0} de ani", format); } } + + [Fact] + public void CanGetCultureSpecificTranslationsWithExplicitCulture() + { + var format = Resources.GetResource("DateHumanize_MultipleYearsAgo_Above20", new CultureInfo("ro")); + Assert.Equal("acum {0} de ani", format); + } } } From 0788a38553c0db86fc8ab12e21a5fd995554c016 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Thu, 29 May 2014 19:58:06 +0300 Subject: [PATCH 05/11] Fixed method's comment --- src/Humanizer/Localisation/Resources.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Humanizer/Localisation/Resources.cs b/src/Humanizer/Localisation/Resources.cs index 6eafd3cfd..631f5b525 100644 --- a/src/Humanizer/Localisation/Resources.cs +++ b/src/Humanizer/Localisation/Resources.cs @@ -14,8 +14,8 @@ public static class Resources /// Returns the value of the specified string resource /// /// The name of the resource to retrieve. - /// The culture of the resource to retrieve. - /// The value of the resource localized for the caller's current UI culture. + /// 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. public static string GetResource(string resourceKey, CultureInfo culture = null) { return ResourceManager.GetString(resourceKey, culture); From ed4121cf5e617957769876b596a538ebb07fc217 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 30 May 2014 14:38:34 +0300 Subject: [PATCH 06/11] Only using optional "Culture" parameter for public methods --- src/Humanizer/Configuration/Configurator.cs | 4 ++-- src/Humanizer/Configuration/LocaliserRegistry.cs | 4 ++-- src/Humanizer/TimeSpanHumanizeExtensions.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Humanizer/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs index 581fb7f01..afadff4f5 100644 --- a/src/Humanizer/Configuration/Configurator.cs +++ b/src/Humanizer/Configuration/Configurator.cs @@ -63,8 +63,8 @@ internal static ICollectionFormatter CollectionFormatter /// /// The formatter to be used /// - /// The culture to retrieve formatter for. If not specified, current thread's UI culture is used. - internal static IFormatter GetFormatter(CultureInfo culture = null) + /// The culture to retrieve formatter for. Null means that current thread's UI culture should be used. + internal static IFormatter GetFormatter(CultureInfo culture) { return Formatters.ResolveForCulture(culture); } diff --git a/src/Humanizer/Configuration/LocaliserRegistry.cs b/src/Humanizer/Configuration/LocaliserRegistry.cs index a0dab6ec0..abf4f407e 100644 --- a/src/Humanizer/Configuration/LocaliserRegistry.cs +++ b/src/Humanizer/Configuration/LocaliserRegistry.cs @@ -27,14 +27,14 @@ public LocaliserRegistry(TLocaliser defaultLocaliser) /// public TLocaliser ResolveForUiCulture() { - return ResolveForCulture(); + return ResolveForCulture(null); } /// /// Gets the localiser for the specified culture /// /// The culture to retrieve localiser for. If not specified, current thread's UI culture is used. - public TLocaliser ResolveForCulture(CultureInfo culture = null) + public TLocaliser ResolveForCulture(CultureInfo culture) { culture = culture ?? CultureInfo.CurrentUICulture; diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index 660b3be2e..e84f64509 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -38,7 +38,7 @@ public static string Humanize(this TimeSpan timeSpan, int precision = 1) private static string GetTimePart(TimeSpan timespan) { - var formatter = Configurator.GetFormatter(); + var formatter = Configurator.GetFormatter(null); if (timespan.Days >= 7) return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7); From 1f8ad39f68d4600d0c6241eb5b56d65a0b2441f3 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 30 May 2014 14:56:13 +0300 Subject: [PATCH 07/11] + unit tests for DateTime.Humanize with explicit culure --- src/Humanizer.Tests/DateHumanize.cs | 19 ++++++++++--------- .../DateHumanizeDefaultStrategyTests.cs | 12 +++++++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Humanizer.Tests/DateHumanize.cs b/src/Humanizer.Tests/DateHumanize.cs index e498a2904..51be3f6fa 100644 --- a/src/Humanizer.Tests/DateHumanize.cs +++ b/src/Humanizer.Tests/DateHumanize.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Humanizer.Configuration; using Humanizer.DateTimeHumanizeStrategy; using Humanizer.Localisation; @@ -8,26 +9,26 @@ namespace Humanizer.Tests { public class DateHumanize { - static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) + static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow, CultureInfo culture) { var utcNow = DateTime.UtcNow; var localNow = DateTime.Now; // feels like the only way to avoid breaking tests because CPU ticks over is to inject the base date - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); - Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(utcDate: false, dateToCompareAgainst: localNow)); + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow, culture: culture)); + Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(utcDate: false, dateToCompareAgainst: localNow, culture: culture)); } - static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) + static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow, CultureInfo culture) { var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow)); - Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now)); + Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize(utcDate: true, dateToCompareAgainst: utcNow, culture: culture)); + Assert.Equal(expectedString, now.Add(deltaFromNow).Humanize(false, now, culture: culture)); } - public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null) + public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null, CultureInfo culture = null) { if (precision.HasValue) Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision.Value); @@ -65,8 +66,8 @@ public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Te break; } - VerifyWithCurrentDate(expectedString, deltaFromNow); - VerifyWithDateInjection(expectedString, deltaFromNow); + VerifyWithCurrentDate(expectedString, deltaFromNow, culture); + VerifyWithDateInjection(expectedString, deltaFromNow, culture); } } } \ No newline at end of file diff --git a/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs b/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs index 432f5ef97..230a02b3b 100644 --- a/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs +++ b/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs @@ -1,4 +1,5 @@ -using Humanizer.Localisation; +using System.Globalization; +using Humanizer.Localisation; using Xunit; using Xunit.Extensions; @@ -138,5 +139,14 @@ public void Now() { DateHumanize.Verify("now", 0, TimeUnit.Year, Tense.Future); } + + [Theory] + [InlineData(1, TimeUnit.Year, Tense.Future, "en-US", "one year from now")] + [InlineData(40, TimeUnit.Second, Tense.Past, "ru-RU", "40 секунд назад")] + [InlineData(2, TimeUnit.Day, Tense.Past, "sv-SE", "för 2 dagar sedan")] + public void ExplicitCultureIsUsed(int unit, TimeUnit timeUnit, Tense tense, string culture, string expected) + { + DateHumanize.Verify(expected, unit, timeUnit, tense, culture: new CultureInfo(culture)); + } } } From 3bb7fec128eccb7691dd2660e004b93d0447217c Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 30 May 2014 15:00:52 +0300 Subject: [PATCH 08/11] Updated readme to reflect new "culture" parameter for DateTime.Humanize --- readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index d294af357..55bdfaae2 100644 --- a/readme.md +++ b/readme.md @@ -233,10 +233,12 @@ DateTime.UtcNow.AddHours(30).Humanize() => "tomorrow" DateTime.UtcNow.AddHours(2).Humanize() => "2 hours from now" ``` -Humanizer supports local as well as UTC dates. You could also provide the date you want the input date to be compared against. If null, it will use the current date as comparison base. Here is the API signature: +Humanizer supports local as well as UTC dates. You could also provide the date you want the input date to be compared against. If null, it will use the current date as comparison base. +Also, culture to use can be specified explicitly. If it is not, current thread's current UI culture is used. +Here is the API signature: ```C# -public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null) +public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null, CultureInfo culture = null) ``` Many localizations are available for this method. Here is a few examples: From 464e7b428c0da47a08dba0b481f9a1567bdb5689 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Fri, 30 May 2014 15:02:45 +0300 Subject: [PATCH 09/11] Updated release_notes to reflect new "culture" parameter for DateTime.Humanize --- release_notes.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/release_notes.md b/release_notes.md index 2ca9a2170..c2736a496 100644 --- a/release_notes.md +++ b/release_notes.md @@ -4,7 +4,8 @@ - [#281](https://github.com/Mehdik/Humanizer/pull/281): Changed the logic for handling hyphenation and large numbers ending in twelve for English ordinal words; e.g. before "twenty first" now "twenty-first" - [#278](https://github.com/MehdiK/Humanizer/pull/278): Changed DefaultDateTimeHumanizeStrategy to turn 60 min to one hour not 45 - [#283](https://github.com/MehdiK/Humanizer/pull/283): Added Neutral nb support for DateTime and TimeSpan Humanize - + - [#286](https://github.com/MehdiK/Humanizer/pull/286): Added optional Culture parameter to DateTime.Humanize + [Commits](https://github.com/MehdiK/Humanizer/compare/v1.26.1...master) ###v1.26.1 - 2014-05-20 @@ -26,7 +27,7 @@ [Commits](https://github.com/MehdiK/Humanizer/compare/v1.24.1...v1.25.4) ###v1.24.1 - 2014-04-21 - - [#232](https://github.com/Mehdik/Humanizer/pull/232): Adding code & tests to handle Arabic numbers to ordinal + - [#232](https://github.com/Mehdik/Humanizer/pull/232): Adding code & tests to handle Arabic numbers to ordinal - [#235](https://github.com/Mehdik/Humanizer/pull/235): Fixed the conversion for "1 millon" in SpanishNumberToWordsConverter - [#233](https://github.com/Mehdik/Humanizer/pull/233): Added build.cmd and Verify build configuration for strict project build and analysis @@ -49,8 +50,8 @@ - [#199](https://github.com/MehdiK/Humanizer/pull/199): Added Hebrew Number to words (both genders) - [#202](https://github.com/MehdiK/Humanizer/pull/202): Fixed typo sekunttia -> sekuntia (Finnish translation) - [#203](https://github.com/MehdiK/Humanizer/pull/203): Added feminine gender for french ordinal words - - [#208](https://github.com/MehdiK/Humanizer/pull/208): Added Hebrew implementation of future DateTime - + - [#208](https://github.com/MehdiK/Humanizer/pull/208): Added Hebrew implementation of future DateTime + [Commits](https://github.com/MehdiK/Humanizer/compare/v1.21.1...v1.22.1) ###v1.21.1 - 2014-04-12 @@ -60,7 +61,7 @@ - [#190](https://github.com/MehdiK/Humanizer/pull/190): Added French translation for ToWords and ToOrdinalWords - [#179](https://github.com/MehdiK/Humanizer/pull/179): Added Hungarian localisation - [#181](https://github.com/Mehdik/Humanizer/pull/181): Added Bulgarian localization, date and timespan tests - - [#141](https://github.com/MehdiK/Humanizer/pull/141): Added Indonesian localization + - [#141](https://github.com/MehdiK/Humanizer/pull/141): Added Indonesian localization - [#148](https://github.com/Mehdik/Humanizer/pull/148): Added Hebrew localization for date and timespan [Commits](https://github.com/MehdiK/Humanizer/compare/v1.20.15...v1.21.1) @@ -82,12 +83,12 @@ [Commits](https://github.com/MehdiK/Humanizer/compare/v1.19.1...v1.20.2) ###v1.19.1 - 2014-04-10 - - [#149](https://github.com/MehdiK/Humanizer/pull/149): Improved & refactored number to words localisation + - [#149](https://github.com/MehdiK/Humanizer/pull/149): Improved & refactored number to words localisation - [#143](https://github.com/MehdiK/Humanizer/pull/143): Added Russian translation for future DateTime, TimeSpan and Now - [#144](https://github.com/MehdiK/Humanizer/pull/144): Added Danish localization (strings, tests) - [#146](https://github.com/MehdiK/Humanizer/pull/146): Added Spanish translation for future DateTime, TimeSpan and Now - - + + [Commits](https://github.com/MehdiK/Humanizer/compare/v1.18.1...v1.19.1) ###v1.18.1 - 2014-04-09 @@ -120,7 +121,7 @@ ###v1.14.1 - 2014-03-26 - [#108](https://github.com/MehdiK/Humanizer/pull/108): Added support for custom description attributes - - [#106](https://github.com/MehdiK/Humanizer/pull/106): + - [#106](https://github.com/MehdiK/Humanizer/pull/106): - Refactored IFormatter and DefaultFormatter - Refactored `DateTime.Humanize` and `TimeSpan.Humanize` - Changed `ResourceKeys` to use a dynamic key generation @@ -179,7 +180,7 @@ If you were catching `CannotMapToTargetException` on a `DehumanizeTo` call, that ####Potential breaking change The return type of `DehumanizeTo` was changed from `Enum` to `TTargetEnum` to make the API a lot easier to work with. That also potentially means that your calls to the old method may be broken. -Depending on how you were using the method you might have to either drop the now redundant cast to `TTargetEnum` in your code, or +Depending on how you were using the method you might have to either drop the now redundant cast to `TTargetEnum` in your code, or fix it based on your requirements. [Commits](https://github.com/MehdiK/Humanizer/compare/v1.5.1...v1.6.1) @@ -205,7 +206,7 @@ fix it based on your requirements. ###v1.1.0 - 2014-01-01 - [#37](https://github.com/MehdiK/Humanizer/pull/37): added `ToQuantity` method - - [#43](https://github.com/MehdiK/Humanizer/pull/43): + - [#43](https://github.com/MehdiK/Humanizer/pull/43): - added `Plurality` enum - can call `Singularize` on singular and `Pluralize` on plural words - `ToQuantity` can be called on words with unknown plurality @@ -214,7 +215,7 @@ fix it based on your requirements. ###v1.0.29 - 2013-12-25 - [#26](https://github.com/MehdiK/Humanizer/pull/26): added Norwegian (nb-NO) localization for `DateTime.Humanize()` - - [#33](https://github.com/MehdiK/Humanizer/pull/33): + - [#33](https://github.com/MehdiK/Humanizer/pull/33): - changed to Portable Class Library with support for .Net 4+, SilverLight 5, Windows Phone 8 and Win Store applications - symbols nuget package is published so you can step through Humanizer code while debugging your code From 70f8f72ea5360a8738463f70ebe9e49a5c8e79e8 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Sun, 1 Jun 2014 07:46:40 +0300 Subject: [PATCH 10/11] Added optional Culture parameter to TimeSpan.Humanize --- readme.md | 8 ++++++ release_notes.md | 2 +- ...provalTest.approve_public_api.approved.txt | 10 +++---- src/Humanizer.Tests/TimeSpanHumanizeTests.cs | 11 ++++++++ .../Formatters/DefaultFormatter.cs | 14 +++++----- .../Localisation/Formatters/IFormatter.cs | 6 +++-- src/Humanizer/TimeSpanHumanizeExtensions.cs | 26 ++++++++++--------- 7 files changed, 51 insertions(+), 26 deletions(-) diff --git a/readme.md b/readme.md index 55bdfaae2..267a6195d 100644 --- a/readme.md +++ b/readme.md @@ -315,6 +315,14 @@ TimeSpan.FromMilliseconds(2).Humanize() => "2 milisekundy" TimeSpan.FromMilliseconds(5).Humanize() => "5 milisekúnd" ``` +Culture to use can be specified explicitly. If it is not, current thread's current UI culture is used. Example: + +```C# + +TimeSpan.FromDays(1).Humanize(culture: "ru-RU") => "один день" + +``` + ###Humanize Collections You can call `Humanize` on any `IEnumerable` to get a nicely formatted string representing the objects in the collection. By default `ToString()` will be called on each item to get its representation but a formatting function may be passed to `Humanize` instead. Additionally, a default separator is provided("and" in English), but a different separator may be passed into `Humanize`. diff --git a/release_notes.md b/release_notes.md index c2736a496..9a7730feb 100644 --- a/release_notes.md +++ b/release_notes.md @@ -4,7 +4,7 @@ - [#281](https://github.com/Mehdik/Humanizer/pull/281): Changed the logic for handling hyphenation and large numbers ending in twelve for English ordinal words; e.g. before "twenty first" now "twenty-first" - [#278](https://github.com/MehdiK/Humanizer/pull/278): Changed DefaultDateTimeHumanizeStrategy to turn 60 min to one hour not 45 - [#283](https://github.com/MehdiK/Humanizer/pull/283): Added Neutral nb support for DateTime and TimeSpan Humanize - - [#286](https://github.com/MehdiK/Humanizer/pull/286): Added optional Culture parameter to DateTime.Humanize + - [#286](https://github.com/MehdiK/Humanizer/pull/286): Added optional Culture parameter to DateTime.Humanize & TimeSpan.Humanize [Commits](https://github.com/MehdiK/Humanizer/compare/v1.26.1...master) 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 d7b6055ab..6d92e8d8c 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -218,16 +218,16 @@ public class DefaultFormatter public DefaultFormatter() { } public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture) { } public string DateHumanize_Now(System.Globalization.CultureInfo culture) { } - public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit) { } - public string TimeSpanHumanize_Zero() { } + public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, System.Globalization.CultureInfo culture) { } + public string TimeSpanHumanize_Zero(System.Globalization.CultureInfo culture) { } } public interface IFormatter { string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture); string DateHumanize_Now(System.Globalization.CultureInfo culture); - string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit); - string TimeSpanHumanize_Zero(); + string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, System.Globalization.CultureInfo culture); + string TimeSpanHumanize_Zero(System.Globalization.CultureInfo culture); } public interface INumberToWordsConverter @@ -367,7 +367,7 @@ public class StringHumanizeExtensions public class TimeSpanHumanizeExtensions { - public string Humanize(System.TimeSpan timeSpan, int precision) { } + public string Humanize(System.TimeSpan timeSpan, int precision, System.Globalization.CultureInfo culture) { } } public class To diff --git a/src/Humanizer.Tests/TimeSpanHumanizeTests.cs b/src/Humanizer.Tests/TimeSpanHumanizeTests.cs index e386c4fca..118aa2328 100644 --- a/src/Humanizer.Tests/TimeSpanHumanizeTests.cs +++ b/src/Humanizer.Tests/TimeSpanHumanizeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Xunit; using Xunit.Extensions; @@ -110,5 +111,15 @@ public void NoTime() var actual = noTime.Humanize(); Assert.Equal("no time", actual); } + + [Theory] + [InlineData(1, "en-US", "1 millisecond")] + [InlineData(6 * 24 * 60 * 60 * 1000, "ru-RU", "6 дней")] + [InlineData(11 * 60 * 60 * 1000, "ar", "11 ساعة")] + public void ExplicitCultureIsUsed(int ms, string culture, string expected) + { + var actual = TimeSpan.FromMilliseconds(ms).Humanize(culture: new CultureInfo(culture)); + Assert.Equal(expected, actual); + } } } diff --git a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs index 66ec76a77..8ebc41d8d 100644 --- a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs @@ -33,10 +33,11 @@ public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int u /// /// 0 seconds /// + /// /// Returns 0 seconds as the string representation of Zero TimeSpan - public virtual string TimeSpanHumanize_Zero() + public virtual string TimeSpanHumanize_Zero(CultureInfo culture) { - return GetResourceForTimeSpan(TimeUnit.Millisecond, 0); + return GetResourceForTimeSpan(TimeUnit.Millisecond, 0, culture); } /// @@ -44,10 +45,11 @@ public virtual string TimeSpanHumanize_Zero() /// /// /// + /// /// - public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit) + public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit, CultureInfo culture) { - return GetResourceForTimeSpan(timeUnit, unit); + return GetResourceForTimeSpan(timeUnit, unit, culture); } private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, CultureInfo culture) @@ -56,10 +58,10 @@ private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); } - private string GetResourceForTimeSpan(TimeUnit unit, int count) + private string GetResourceForTimeSpan(TimeUnit unit, int count, CultureInfo culture) { string resourceKey = ResourceKeys.TimeSpanHumanize.GetResourceKey(unit, count); - return count == 1 ? Format(resourceKey, null) : Format(resourceKey, count, null); + return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); } /// diff --git a/src/Humanizer/Localisation/Formatters/IFormatter.cs b/src/Humanizer/Localisation/Formatters/IFormatter.cs index 462efef21..4f0a6bb27 100644 --- a/src/Humanizer/Localisation/Formatters/IFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/IFormatter.cs @@ -29,15 +29,17 @@ public interface IFormatter /// /// 0 seconds /// + /// /// Returns 0 seconds as the string representation of Zero TimeSpan - string TimeSpanHumanize_Zero(); + string TimeSpanHumanize_Zero(CultureInfo culture); /// /// Returns the string representation of the provided TimeSpan /// /// /// + /// /// - string TimeSpanHumanize(TimeUnit timeUnit, int unit); + string TimeSpanHumanize(TimeUnit timeUnit, int unit, CultureInfo culture); } } diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index e84f64509..f4ef4d60a 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text; using Humanizer.Configuration; using Humanizer.Localisation; @@ -15,13 +16,14 @@ public static class TimeSpanHumanizeExtensions /// /// /// The maximum number of time units to return. Defaulted is 1 which means the largest unit is returned + /// Culture to use. If null, current thread's UI culture is used. /// - public static string Humanize(this TimeSpan timeSpan, int precision = 1) + public static string Humanize(this TimeSpan timeSpan, int precision = 1, CultureInfo culture = null) { var result = new StringBuilder(); for (int i = 0; i < precision; i++) { - var timePart = GetTimePart(timeSpan); + var timePart = GetTimePart(timeSpan, culture); if (result.Length > 0) result.Append(", "); @@ -36,28 +38,28 @@ public static string Humanize(this TimeSpan timeSpan, int precision = 1) return result.ToString(); } - private static string GetTimePart(TimeSpan timespan) + private static string GetTimePart(TimeSpan timespan, CultureInfo culture) { - var formatter = Configurator.GetFormatter(null); + var formatter = Configurator.GetFormatter(culture); if (timespan.Days >= 7) - return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7); + return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7, culture); - if(timespan.Days >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Day, timespan.Days); + if (timespan.Days >= 1) + return formatter.TimeSpanHumanize(TimeUnit.Day, timespan.Days, culture); if (timespan.Hours >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Hour, timespan.Hours); + return formatter.TimeSpanHumanize(TimeUnit.Hour, timespan.Hours, culture); if (timespan.Minutes >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Minute, timespan.Minutes); + return formatter.TimeSpanHumanize(TimeUnit.Minute, timespan.Minutes, culture); if (timespan.Seconds >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Second, timespan.Seconds); + return formatter.TimeSpanHumanize(TimeUnit.Second, timespan.Seconds, culture); if (timespan.Milliseconds >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Millisecond, timespan.Milliseconds); + return formatter.TimeSpanHumanize(TimeUnit.Millisecond, timespan.Milliseconds, culture); - return formatter.TimeSpanHumanize_Zero(); + return formatter.TimeSpanHumanize_Zero(culture); } static TimeSpan TakeOutTheLargestUnit(TimeSpan timeSpan) From c8bcd8782a42ce9717fe14510761df3fbea43dc3 Mon Sep 17 00:00:00 2001 From: Dmitry Gokun Date: Tue, 3 Jun 2014 22:32:00 +0300 Subject: [PATCH 11/11] 1) Made Date/Time formatters to own "culture"; 2) Explicitly registering DefaultFormatter's for the supported languages in FormatterRegistry's constructor --- ...provalTest.approve_public_api.approved.txt | 18 +++---- .../DateHumanizeDefaultStrategyTests.cs | 2 +- .../Configuration/FormatterRegistry.cs | 50 ++++++++++++++++--- .../DefaultDateTimeHumanizeStrategy.cs | 24 ++++----- .../PrecisionDateTimeHumanizeStrategy.cs | 14 +++--- .../Formatters/ArabicFormatter.cs | 5 ++ .../Formatters/CzechSlovakPolishFormatter.cs | 5 ++ .../Formatters/DefaultFormatter.cs | 49 ++++++++++-------- .../Formatters/HebrewFormatter.cs | 5 ++ .../Localisation/Formatters/IFormatter.cs | 16 ++---- .../Formatters/RomanianFormatter.cs | 5 ++ .../Formatters/RussianFormatter.cs | 5 ++ .../Formatters/SerbianFormatter.cs | 5 ++ .../Formatters/SlovenianFormatter.cs | 5 ++ src/Humanizer/TimeSpanHumanizeExtensions.cs | 14 +++--- 15 files changed, 146 insertions(+), 76 deletions(-) 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 6d92e8d8c..a37baef21 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -215,19 +215,19 @@ public interface ICollectionFormatter public class DefaultFormatter { - public DefaultFormatter() { } - public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture) { } - public string DateHumanize_Now(System.Globalization.CultureInfo culture) { } - public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, System.Globalization.CultureInfo culture) { } - public string TimeSpanHumanize_Zero(System.Globalization.CultureInfo culture) { } + public DefaultFormatter(string localeCode) { } + public string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit) { } + public string DateHumanize_Now() { } + public string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit) { } + public string TimeSpanHumanize_Zero() { } } public interface IFormatter { - string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit, System.Globalization.CultureInfo culture); - string DateHumanize_Now(System.Globalization.CultureInfo culture); - string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit, System.Globalization.CultureInfo culture); - string TimeSpanHumanize_Zero(System.Globalization.CultureInfo culture); + string DateHumanize(Humanizer.Localisation.TimeUnit timeUnit, Humanizer.Localisation.Tense timeUnitTense, int unit); + string DateHumanize_Now(); + string TimeSpanHumanize(Humanizer.Localisation.TimeUnit timeUnit, int unit); + string TimeSpanHumanize_Zero(); } public interface INumberToWordsConverter diff --git a/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs b/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs index 230a02b3b..60d994329 100644 --- a/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs +++ b/src/Humanizer.Tests/DateHumanizeDefaultStrategyTests.cs @@ -142,7 +142,7 @@ public void Now() [Theory] [InlineData(1, TimeUnit.Year, Tense.Future, "en-US", "one year from now")] - [InlineData(40, TimeUnit.Second, Tense.Past, "ru-RU", "40 секунд назад")] + [InlineData(40, TimeUnit.Second, Tense.Past, "ru-RU", "40 секунд назад")] [InlineData(2, TimeUnit.Day, Tense.Past, "sv-SE", "för 2 dagar sedan")] public void ExplicitCultureIsUsed(int unit, TimeUnit timeUnit, Tense tense, string culture, string expected) { diff --git a/src/Humanizer/Configuration/FormatterRegistry.cs b/src/Humanizer/Configuration/FormatterRegistry.cs index d9c2f6734..927d63a09 100644 --- a/src/Humanizer/Configuration/FormatterRegistry.cs +++ b/src/Humanizer/Configuration/FormatterRegistry.cs @@ -4,17 +4,53 @@ namespace Humanizer.Configuration { internal class FormatterRegistry : LocaliserRegistry { - public FormatterRegistry() : base(new DefaultFormatter()) + public FormatterRegistry() : base(new DefaultFormatter("en-US")) { - Register("ro"); - Register("ru"); + RegisterDefaultFormatter("af"); Register("ar"); + RegisterDefaultFormatter("bg"); + RegisterCzechSlovakPolishFormatter("cs"); + RegisterDefaultFormatter("da"); + RegisterDefaultFormatter("de"); + RegisterDefaultFormatter("el"); + RegisterDefaultFormatter("es"); + RegisterDefaultFormatter("fa"); + RegisterDefaultFormatter("fi-FI"); + RegisterDefaultFormatter("fr"); + RegisterDefaultFormatter("fr-BE"); Register("he"); - Register("sk"); - Register("cs"); - Register("pl"); - Register("sr"); + RegisterDefaultFormatter("hu"); + RegisterDefaultFormatter("id"); + RegisterDefaultFormatter("ja"); + RegisterDefaultFormatter("nb"); + RegisterDefaultFormatter("nb-NO"); + RegisterDefaultFormatter("nl"); + RegisterCzechSlovakPolishFormatter("pl"); + RegisterDefaultFormatter("pt-BR"); + Register("ro"); + Register("ru"); + RegisterCzechSlovakPolishFormatter("sk"); Register("sl"); + RegisterSerbianFormatter("sr"); + RegisterSerbianFormatter("sr-Latn"); + RegisterDefaultFormatter("sv"); + RegisterDefaultFormatter("tr"); + RegisterDefaultFormatter("vi"); + } + + private void RegisterDefaultFormatter(string localeCode) + { + Register(() => new DefaultFormatter(localeCode), localeCode); + } + + private void RegisterCzechSlovakPolishFormatter(string localeCode) + { + Register(() => new CzechSlovakPolishFormatter(localeCode), localeCode); + } + + private void RegisterSerbianFormatter(string localeCode) + { + Register(() => new SerbianFormatter(localeCode), localeCode); } } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs index 4ee8195f9..42de15b8b 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs @@ -26,46 +26,46 @@ public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo cult var formatter = Configurator.GetFormatter(culture); if (ts.TotalMilliseconds < 500) - return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, culture); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); if (ts.TotalSeconds < 60) - return formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds, culture); + return formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds); if (ts.TotalSeconds < 120) - return formatter.DateHumanize(TimeUnit.Minute, tense, 1, culture); + return formatter.DateHumanize(TimeUnit.Minute, tense, 1); if (ts.TotalMinutes < 60) - return formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes, culture); + return formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes); if (ts.TotalMinutes < 90) - return formatter.DateHumanize(TimeUnit.Hour, tense, 1, culture); + return formatter.DateHumanize(TimeUnit.Hour, tense, 1); if (ts.TotalHours < 24) - return formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours, culture); + return formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours); if (ts.TotalHours < 48) - return formatter.DateHumanize(TimeUnit.Day, tense, 1, culture); + return formatter.DateHumanize(TimeUnit.Day, tense, 1); if (ts.TotalDays < 28) - return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, culture); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); if (ts.TotalDays >= 28 && ts.TotalDays < 30) { if (comparisonBase.Date.AddMonths(tense == Tense.Future ? 1 : -1) == input.Date) - return formatter.DateHumanize(TimeUnit.Month, tense, 1, culture); - return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, culture); + return formatter.DateHumanize(TimeUnit.Month, tense, 1); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); } if (ts.TotalDays < 345) { int months = Convert.ToInt32(Math.Floor(ts.TotalDays / 29.5)); - return formatter.DateHumanize(TimeUnit.Month, tense, months, culture); + return formatter.DateHumanize(TimeUnit.Month, tense, months); } int years = Convert.ToInt32(Math.Floor(ts.TotalDays / 365)); if (years == 0) years = 1; - return formatter.DateHumanize(TimeUnit.Year, tense, years, culture); + return formatter.DateHumanize(TimeUnit.Year, tense, years); } } } \ No newline at end of file diff --git a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs index cec212dd3..64e9ba567 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs @@ -62,13 +62,13 @@ public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo cult // start computing result from larger units to smaller ones var formatter = Configurator.GetFormatter(culture); - if (years > 0) return formatter.DateHumanize(TimeUnit.Year, tense, years, culture); - if (months > 0) return formatter.DateHumanize(TimeUnit.Month, tense, months, culture); - if (days > 0) return formatter.DateHumanize(TimeUnit.Day, tense, days, culture); - if (hours > 0) return formatter.DateHumanize(TimeUnit.Hour, tense, hours, culture); - if (minutes > 0) return formatter.DateHumanize(TimeUnit.Minute, tense, minutes, culture); - if (seconds > 0) return formatter.DateHumanize(TimeUnit.Second, tense, seconds, culture); - return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, culture); + if (years > 0) return formatter.DateHumanize(TimeUnit.Year, tense, years); + if (months > 0) return formatter.DateHumanize(TimeUnit.Month, tense, months); + if (days > 0) return formatter.DateHumanize(TimeUnit.Day, tense, days); + if (hours > 0) return formatter.DateHumanize(TimeUnit.Hour, tense, hours); + if (minutes > 0) return formatter.DateHumanize(TimeUnit.Minute, tense, minutes); + if (seconds > 0) return formatter.DateHumanize(TimeUnit.Second, tense, seconds); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); } } } \ No newline at end of file diff --git a/src/Humanizer/Localisation/Formatters/ArabicFormatter.cs b/src/Humanizer/Localisation/Formatters/ArabicFormatter.cs index eb71f4389..fb210e91d 100644 --- a/src/Humanizer/Localisation/Formatters/ArabicFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/ArabicFormatter.cs @@ -5,6 +5,11 @@ internal class ArabicFormatter : DefaultFormatter private const string DualPostfix = "_Dual"; private const string PluralPostfix = "_Plural"; + public ArabicFormatter() + : base("ar") + { + } + protected override string GetResourceKey(string resourceKey, int number) { //In Arabic pluralization 2 entities gets a different word. diff --git a/src/Humanizer/Localisation/Formatters/CzechSlovakPolishFormatter.cs b/src/Humanizer/Localisation/Formatters/CzechSlovakPolishFormatter.cs index 84918eda5..4cd4d7d56 100644 --- a/src/Humanizer/Localisation/Formatters/CzechSlovakPolishFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/CzechSlovakPolishFormatter.cs @@ -4,6 +4,11 @@ internal class CzechSlovakPolishFormatter : DefaultFormatter { private const string PaucalPostfix = "_Paucal"; + public CzechSlovakPolishFormatter(string localeCode) + : base(localeCode) + { + } + protected override string GetResourceKey(string resourceKey, int number) { if (number > 1 && number < 5) diff --git a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs index 8ebc41d8d..f0eaaa0d0 100644 --- a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs @@ -7,14 +7,24 @@ namespace Humanizer.Localisation.Formatters /// public class DefaultFormatter : IFormatter { + private readonly CultureInfo _culture; + + /// + /// Constructor. + /// + /// Name of the culture to use. + public DefaultFormatter(string localeCode) + { + _culture = new CultureInfo(localeCode); + } + /// /// Now /// - /// /// Returns Now - public virtual string DateHumanize_Now(CultureInfo culture) + public virtual string DateHumanize_Now() { - return GetResourceForDate(TimeUnit.Millisecond, Tense.Past, 0, culture); + return GetResourceForDate(TimeUnit.Millisecond, Tense.Past, 0); } /// @@ -23,21 +33,19 @@ public virtual string DateHumanize_Now(CultureInfo culture) /// /// /// - /// /// - public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, CultureInfo culture) + public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit) { - return GetResourceForDate(timeUnit, timeUnitTense, unit, culture); + return GetResourceForDate(timeUnit, timeUnitTense, unit); } /// /// 0 seconds /// - /// /// Returns 0 seconds as the string representation of Zero TimeSpan - public virtual string TimeSpanHumanize_Zero(CultureInfo culture) + public virtual string TimeSpanHumanize_Zero() { - return GetResourceForTimeSpan(TimeUnit.Millisecond, 0, culture); + return GetResourceForTimeSpan(TimeUnit.Millisecond, 0); } /// @@ -45,34 +53,32 @@ public virtual string TimeSpanHumanize_Zero(CultureInfo culture) /// /// /// - /// /// - public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit, CultureInfo culture) + public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit) { - return GetResourceForTimeSpan(timeUnit, unit, culture); + return GetResourceForTimeSpan(timeUnit, unit); } - private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, CultureInfo culture) + private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count) { string resourceKey = ResourceKeys.DateHumanize.GetResourceKey(unit, timeUnitTense: timeUnitTense, count: count); - return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); + return count == 1 ? Format(resourceKey) : Format(resourceKey, count); } - private string GetResourceForTimeSpan(TimeUnit unit, int count, CultureInfo culture) + private string GetResourceForTimeSpan(TimeUnit unit, int count) { string resourceKey = ResourceKeys.TimeSpanHumanize.GetResourceKey(unit, count); - return count == 1 ? Format(resourceKey, culture) : Format(resourceKey, count, culture); + return count == 1 ? Format(resourceKey) : Format(resourceKey, count); } /// /// /// /// - /// /// - protected virtual string Format(string resourceKey, CultureInfo culture) + protected virtual string Format(string resourceKey) { - return Resources.GetResource(GetResourceKey(resourceKey), culture); + return Resources.GetResource(GetResourceKey(resourceKey), _culture); } /// @@ -80,11 +86,10 @@ protected virtual string Format(string resourceKey, CultureInfo culture) /// /// /// - /// /// - protected virtual string Format(string resourceKey, int number, CultureInfo culture) + protected virtual string Format(string resourceKey, int number) { - return Resources.GetResource(GetResourceKey(resourceKey, number), culture).FormatWith(number); + return Resources.GetResource(GetResourceKey(resourceKey, number), _culture).FormatWith(number); } /// diff --git a/src/Humanizer/Localisation/Formatters/HebrewFormatter.cs b/src/Humanizer/Localisation/Formatters/HebrewFormatter.cs index 23542079f..ef151ef2c 100644 --- a/src/Humanizer/Localisation/Formatters/HebrewFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/HebrewFormatter.cs @@ -5,6 +5,11 @@ internal class HebrewFormatter : DefaultFormatter private const string DualPostfix = "_Dual"; private const string PluralPostfix = "_Plural"; + public HebrewFormatter() + : base("he") + { + } + protected override string GetResourceKey(string resourceKey, int number) { //In Hebrew pluralization 2 entities gets a different word. diff --git a/src/Humanizer/Localisation/Formatters/IFormatter.cs b/src/Humanizer/Localisation/Formatters/IFormatter.cs index 4f0a6bb27..887209658 100644 --- a/src/Humanizer/Localisation/Formatters/IFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/IFormatter.cs @@ -1,6 +1,4 @@ -using System.Globalization; - -namespace Humanizer.Localisation.Formatters +namespace Humanizer.Localisation.Formatters { /// /// Implement this interface if your language has complex rules around dealing with numbers. @@ -12,9 +10,8 @@ public interface IFormatter /// /// Now /// - /// /// Returns Now - string DateHumanize_Now(CultureInfo culture); + string DateHumanize_Now(); /// /// Returns the string representation of the provided DateTime @@ -22,24 +19,21 @@ public interface IFormatter /// /// /// - /// /// - string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, CultureInfo culture); + string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit); /// /// 0 seconds /// - /// /// Returns 0 seconds as the string representation of Zero TimeSpan - string TimeSpanHumanize_Zero(CultureInfo culture); + string TimeSpanHumanize_Zero(); /// /// Returns the string representation of the provided TimeSpan /// /// /// - /// /// - string TimeSpanHumanize(TimeUnit timeUnit, int unit, CultureInfo culture); + string TimeSpanHumanize(TimeUnit timeUnit, int unit); } } diff --git a/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs b/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs index 0cfd46310..ab0e406e9 100644 --- a/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/RomanianFormatter.cs @@ -4,6 +4,11 @@ internal class RomanianFormatter : DefaultFormatter { private const string Above20PostFix = "_Above20"; + public RomanianFormatter() + : base("ro") + { + } + protected override string GetResourceKey(string resourceKey, int number) { var mod100 = number%100; diff --git a/src/Humanizer/Localisation/Formatters/RussianFormatter.cs b/src/Humanizer/Localisation/Formatters/RussianFormatter.cs index fa17a0eff..14f054706 100644 --- a/src/Humanizer/Localisation/Formatters/RussianFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/RussianFormatter.cs @@ -4,6 +4,11 @@ namespace Humanizer.Localisation.Formatters { internal class RussianFormatter : DefaultFormatter { + public RussianFormatter() + : base("ru") + { + } + protected override string GetResourceKey(string resourceKey, int number) { var grammaticalNumber = RussianGrammaticalNumberDetector.Detect(number); diff --git a/src/Humanizer/Localisation/Formatters/SerbianFormatter.cs b/src/Humanizer/Localisation/Formatters/SerbianFormatter.cs index 8f8ea913e..ebe765abf 100644 --- a/src/Humanizer/Localisation/Formatters/SerbianFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/SerbianFormatter.cs @@ -4,6 +4,11 @@ internal class SerbianFormatter : DefaultFormatter { private const string PaucalPostfix = "_Paucal"; + public SerbianFormatter(string localeCode) + : base(localeCode) + { + } + protected override string GetResourceKey(string resourceKey, int number) { int mod10 = number % 10; diff --git a/src/Humanizer/Localisation/Formatters/SlovenianFormatter.cs b/src/Humanizer/Localisation/Formatters/SlovenianFormatter.cs index 2fe886daf..249f1da56 100644 --- a/src/Humanizer/Localisation/Formatters/SlovenianFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/SlovenianFormatter.cs @@ -5,6 +5,11 @@ internal class SlovenianFormatter : DefaultFormatter private const string DualPostfix = "_Dual"; private const string TrialQuadralPostfix = "_TrialQuadral"; + public SlovenianFormatter() + : base("sl") + { + } + protected override string GetResourceKey(string resourceKey, int number) { if (number == 2) diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index f4ef4d60a..bcf8fc5e8 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -42,24 +42,24 @@ private static string GetTimePart(TimeSpan timespan, CultureInfo culture) { var formatter = Configurator.GetFormatter(culture); if (timespan.Days >= 7) - return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7, culture); + return formatter.TimeSpanHumanize(TimeUnit.Week, timespan.Days/7); if (timespan.Days >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Day, timespan.Days, culture); + return formatter.TimeSpanHumanize(TimeUnit.Day, timespan.Days); if (timespan.Hours >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Hour, timespan.Hours, culture); + return formatter.TimeSpanHumanize(TimeUnit.Hour, timespan.Hours); if (timespan.Minutes >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Minute, timespan.Minutes, culture); + return formatter.TimeSpanHumanize(TimeUnit.Minute, timespan.Minutes); if (timespan.Seconds >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Second, timespan.Seconds, culture); + return formatter.TimeSpanHumanize(TimeUnit.Second, timespan.Seconds); if (timespan.Milliseconds >= 1) - return formatter.TimeSpanHumanize(TimeUnit.Millisecond, timespan.Milliseconds, culture); + return formatter.TimeSpanHumanize(TimeUnit.Millisecond, timespan.Milliseconds); - return formatter.TimeSpanHumanize_Zero(culture); + return formatter.TimeSpanHumanize_Zero(); } static TimeSpan TakeOutTheLargestUnit(TimeSpan timeSpan)