diff --git a/readme.md b/readme.md index eb4c35b3c..e96a64159 100644 --- a/readme.md +++ b/readme.md @@ -370,6 +370,12 @@ Culture to use can be specified explicitly. If it is not, current thread's curre TimeSpan.FromDays(1).Humanize(culture: "ru-RU") => "один день" ``` +In addition, a maximum unit of time may be specified to avoid rolling up to the next largest unit. For example: +```C# +TimeSpan.FromDays(7).Humanize(maxUnit: TimeUnit.Day) => "7 days" // instead of 1 week +TimeSpan.FromMilliseconds(2000).Humanize(maxUnit: TimeUnit.Millisecond) => "2000 milliseconds" // instead of 2 seconds +``` + ###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 afa84d524..311731fbc 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,6 +1,7 @@ ###In Development - [#381](https://github.com/MehdiK/Humanizer/pull/381): Fixes trailing question mark reported in #378. - [#382](https://github.com/MehdiK/Humanizer/pull/382): Fix 90000th and -thousandth in RussianNumberToWordsConverter. + - [#384](https://github.com/MehdiK/Humanizer/pull/384): Added maximum TimeUnit to TimeSpanHumanizeExtensions.Humanize to enable prevention of rolling up the next largest unit of time. [Commits](https://github.com/MehdiK/Humanizer/compare/v1.33.7...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 955bf1c34..17307cfd5 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -468,8 +468,8 @@ public class StringHumanizeExtensions public class TimeSpanHumanizeExtensions { - public string Humanize(System.TimeSpan timeSpan, int precision, System.Globalization.CultureInfo culture) { } - public string Humanize(System.TimeSpan timeSpan, int precision, bool countEmptyUnits, System.Globalization.CultureInfo culture) { } + public string Humanize(System.TimeSpan timeSpan, int precision, System.Globalization.CultureInfo culture, Humanizer.Localisation.TimeUnit maxUnit) { } + public string Humanize(System.TimeSpan timeSpan, int precision, bool countEmptyUnits, System.Globalization.CultureInfo culture, Humanizer.Localisation.TimeUnit maxUnit) { } } public class To diff --git a/src/Humanizer.Tests/TimeSpanHumanizeTests.cs b/src/Humanizer.Tests/TimeSpanHumanizeTests.cs index 345dbd5c2..9108b5a4a 100644 --- a/src/Humanizer.Tests/TimeSpanHumanizeTests.cs +++ b/src/Humanizer.Tests/TimeSpanHumanizeTests.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using Humanizer.Localisation; using Xunit; using Xunit.Extensions; @@ -68,6 +69,18 @@ public void Milliseconds(int ms, string expected) Assert.Equal(expected, actual); } + [Theory] + [InlineData(7 * 24 * 60 * 60 * 1000, "7 days", TimeUnit.Day)] + [InlineData(24 * 60 * 60 * 1000, "24 hours", TimeUnit.Hour)] + [InlineData(60 * 60 * 1000, "60 minutes", TimeUnit.Minute)] + [InlineData(60 * 1000, "60 seconds", TimeUnit.Second)] + [InlineData(1000, "1000 milliseconds", TimeUnit.Millisecond)] + public void TimeSpanWithMaxTimeUnit(int ms, string expected, TimeUnit maxUnit) + { + var actual = TimeSpan.FromMilliseconds(ms).Humanize(maxUnit: maxUnit); + Assert.Equal(expected, actual); + } + [Theory] [InlineData(0, 3, "no time")] [InlineData(0, 2, "no time")] diff --git a/src/Humanizer/TimeSpanHumanizeExtensions.cs b/src/Humanizer/TimeSpanHumanizeExtensions.cs index 6914b9cef..d9fb7d41c 100644 --- a/src/Humanizer/TimeSpanHumanizeExtensions.cs +++ b/src/Humanizer/TimeSpanHumanizeExtensions.cs @@ -19,10 +19,11 @@ 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. + /// The maximum unit of time to output. /// - public static string Humanize(this TimeSpan timeSpan, int precision = 1, CultureInfo culture = null) + public static string Humanize(this TimeSpan timeSpan, int precision = 1, CultureInfo culture = null, TimeUnit maxUnit = TimeUnit.Week) { - return Humanize(timeSpan, precision, false, culture); + return Humanize(timeSpan, precision, false, culture, maxUnit); } /// @@ -32,10 +33,11 @@ public static string Humanize(this TimeSpan timeSpan, int precision = 1, Culture /// The maximum number of time units to return. /// Controls whether empty time units should be counted towards maximum number of time units. Leading empty time units never count. /// Culture to use. If null, current thread's UI culture is used. + /// The maximum unit of time to output. /// - public static string Humanize(this TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo culture = null) + public static string Humanize(this TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo culture = null, TimeUnit maxUnit = TimeUnit.Week) { - var timeParts = GetTimeParts(timeSpan, culture); + var timeParts = GetTimeParts(timeSpan, culture, maxUnit); if (!countEmptyUnits) timeParts = timeParts.Where(x => x != null); timeParts = timeParts.Take(precision); @@ -44,27 +46,27 @@ public static string Humanize(this TimeSpan timeSpan, int precision, bool countE return string.Join(", ", timeParts); } - private static IEnumerable GetTimeParts(TimeSpan timespan, CultureInfo culture) + private static IEnumerable GetTimeParts(TimeSpan timespan, CultureInfo culture, TimeUnit maxUnit) { - var weeks = timespan.Days / 7; - var daysInWeek = timespan.Days % 7; - var hours = timespan.Hours; - var minutes = timespan.Minutes; - var seconds = timespan.Seconds; - var milliseconds = timespan.Milliseconds; + var weeks = maxUnit > TimeUnit.Day ? timespan.Days / 7 : 0; + var days = maxUnit > TimeUnit.Day ? timespan.Days % 7 : (int)timespan.TotalDays; + var hours = maxUnit > TimeUnit.Hour ? timespan.Hours : (int)timespan.TotalHours; + var minutes = maxUnit > TimeUnit.Minute ? timespan.Minutes : (int)timespan.TotalMinutes; + var seconds = maxUnit > TimeUnit.Second ? timespan.Seconds : (int)timespan.TotalSeconds; + var milliseconds = maxUnit > TimeUnit.Millisecond ? timespan.Milliseconds : (int)timespan.TotalMilliseconds; - var outputWeeks = weeks > 0; - var outputDays = outputWeeks || daysInWeek > 0; - var outputHours = outputDays || hours > 0; - var outputMinutes = outputHours || minutes > 0; - var outputSeconds = outputMinutes || seconds > 0; - var outputMilliseconds = outputSeconds || milliseconds > 0; + var outputWeeks = weeks > 0 && maxUnit == TimeUnit.Week; + var outputDays = (outputWeeks || days > 0) && maxUnit >= TimeUnit.Day; + var outputHours = (outputDays || hours > 0) && maxUnit >= TimeUnit.Hour; + var outputMinutes = (outputHours || minutes > 0) && maxUnit >= TimeUnit.Minute; + var outputSeconds = (outputMinutes || seconds > 0) && maxUnit >= TimeUnit.Second; + var outputMilliseconds = (outputSeconds || milliseconds > 0) && maxUnit >= TimeUnit.Millisecond; var formatter = Configurator.GetFormatter(culture); if (outputWeeks) yield return GetTimePart(formatter, TimeUnit.Week, weeks); if (outputDays) - yield return GetTimePart(formatter, TimeUnit.Day, daysInWeek); + yield return GetTimePart(formatter, TimeUnit.Day, days); if (outputHours) yield return GetTimePart(formatter, TimeUnit.Hour, hours); if (outputMinutes)