Skip to content

Commit

Permalink
Merge pull request #403 from danielbrezoi/DanB/Master
Browse files Browse the repository at this point in the history
Added TimeSpan minUnit option #388
  • Loading branch information
MehdiK committed May 24, 2015
2 parents 2ebb647 + 225f96c commit b01cb7d
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 43 deletions.
6 changes: 6 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,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 minimum unit of time may be specified to avoid rolling down to a smaller unit. For example:
```C#
TimeSpan.FromMilliseconds(122500).Humanize(minUnit: TimeUnit.Second) => "2 minutes, 2 seconds" // instead of 2 minutes, 2 seconds, 500 milliseconds
TimeSpan.FromHours(25).Humanize(minUnit: TimeUnit.Day) => "1 Day" //instead of 1 Day, 1 Hour
```

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,8 @@ public class StringHumanizeExtensions

public class TimeSpanHumanizeExtensions
{
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 string Humanize(System.TimeSpan timeSpan, int precision, System.Globalization.CultureInfo culture, Humanizer.Localisation.TimeUnit maxUnit, Humanizer.Localisation.TimeUnit minUnit) { }
public string Humanize(System.TimeSpan timeSpan, int precision, bool countEmptyUnits, System.Globalization.CultureInfo culture, Humanizer.Localisation.TimeUnit maxUnit, Humanizer.Localisation.TimeUnit minUnit) { }
}

public class To
Expand Down
43 changes: 43 additions & 0 deletions src/Humanizer.Tests/TimeSpanHumanizeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,49 @@ public void TimeSpanWithMaxTimeUnit(int ms, string expected, TimeUnit maxUnit)
Assert.Equal(expected, actual);
}

[Theory]
[InlineData(10, "10 milliseconds", TimeUnit.Millisecond)]
[InlineData(10, "no time", TimeUnit.Second)]
[InlineData(10, "no time", TimeUnit.Minute)]
[InlineData(10, "no time", TimeUnit.Hour)]
[InlineData(10, "no time", TimeUnit.Day)]
[InlineData(10, "no time", TimeUnit.Week)]
[InlineData(2500, "2 seconds, 500 milliseconds", TimeUnit.Millisecond)]
[InlineData(2500, "2 seconds", TimeUnit.Second)]
[InlineData(2500, "no time", TimeUnit.Minute)]
[InlineData(2500, "no time", TimeUnit.Hour)]
[InlineData(2500, "no time", TimeUnit.Day)]
[InlineData(2500, "no time", TimeUnit.Week)]
[InlineData(122500, "2 minutes, 2 seconds, 500 milliseconds", TimeUnit.Millisecond)]
[InlineData(122500, "2 minutes, 2 seconds", TimeUnit.Second)]
[InlineData(122500, "2 minutes", TimeUnit.Minute)]
[InlineData(122500, "no time", TimeUnit.Hour)]
[InlineData(122500, "no time", TimeUnit.Day)]
[InlineData(122500, "no time", TimeUnit.Week)]
[InlineData(3722500, "1 hour, 2 minutes, 2 seconds, 500 milliseconds", TimeUnit.Millisecond)]
[InlineData(3722500, "1 hour, 2 minutes, 2 seconds", TimeUnit.Second)]
[InlineData(3722500, "1 hour, 2 minutes", TimeUnit.Minute)]
[InlineData(3722500, "1 hour", TimeUnit.Hour)]
[InlineData(3722500, "no time", TimeUnit.Day)]
[InlineData(3722500, "no time", TimeUnit.Week)]
[InlineData(90122500, "1 day, 1 hour, 2 minutes, 2 seconds, 500 milliseconds", TimeUnit.Millisecond)]
[InlineData(90122500, "1 day, 1 hour, 2 minutes, 2 seconds", TimeUnit.Second)]
[InlineData(90122500, "1 day, 1 hour, 2 minutes", TimeUnit.Minute)]
[InlineData(90122500, "1 day, 1 hour", TimeUnit.Hour)]
[InlineData(90122500, "1 day", TimeUnit.Day)]
[InlineData(90122500, "no time", TimeUnit.Week)]
[InlineData(694922500, "1 week, 1 day, 1 hour, 2 minutes, 2 seconds, 500 milliseconds", TimeUnit.Millisecond)]
[InlineData(694922500, "1 week, 1 day, 1 hour, 2 minutes, 2 seconds", TimeUnit.Second)]
[InlineData(694922500, "1 week, 1 day, 1 hour, 2 minutes", TimeUnit.Minute)]
[InlineData(694922500, "1 week, 1 day, 1 hour", TimeUnit.Hour)]
[InlineData(694922500, "1 week, 1 day", TimeUnit.Day)]
[InlineData(694922500, "1 week", TimeUnit.Week)]
public void TimeSpanWithMinTimeUnit(int ms, string expected, TimeUnit minUnit)
{
var actual = TimeSpan.FromMilliseconds(ms).Humanize(minUnit: minUnit, precision: 6);
Assert.Equal(expected, actual);
}

[Theory]
[InlineData(0, 3, "no time")]
[InlineData(0, 2, "no time")]
Expand Down
182 changes: 141 additions & 41 deletions src/Humanizer/TimeSpanHumanizeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ namespace Humanizer
/// </summary>
public static class TimeSpanHumanizeExtensions
{
private const int _lastTimeUnitTypeIndexImplemented = (int)TimeUnit.Week;
private const int _daysInAWeek = 7;

/// <summary>
/// Turns a TimeSpan into a human readable form. E.g. 1 day.
/// </summary>
/// <param name="timeSpan"></param>
/// <param name="precision">The maximum number of time units to return. Defaulted is 1 which means the largest unit is returned</param>
/// <param name="culture">Culture to use. If null, current thread's UI culture is used.</param>
/// <param name="maxUnit">The maximum unit of time to output.</param>
/// <param name="minUnit">The minimum unit of time to output.</param>
/// <returns></returns>
public static string Humanize(this TimeSpan timeSpan, int precision = 1, CultureInfo culture = null, TimeUnit maxUnit = TimeUnit.Week)
public static string Humanize(this TimeSpan timeSpan, int precision = 1, CultureInfo culture = null, TimeUnit maxUnit = TimeUnit.Week, TimeUnit minUnit = TimeUnit.Millisecond)
{
return Humanize(timeSpan, precision, false, culture, maxUnit);
return Humanize(timeSpan, precision, false, culture, maxUnit, minUnit);
}

/// <summary>
Expand All @@ -34,56 +38,152 @@ public static string Humanize(this TimeSpan timeSpan, int precision = 1, Culture
/// <param name="countEmptyUnits">Controls whether empty time units should be counted towards maximum number of time units. Leading empty time units never count.</param>
/// <param name="culture">Culture to use. If null, current thread's UI culture is used.</param>
/// <param name="maxUnit">The maximum unit of time to output.</param>
/// <param name="minUnit">The minimum unit of time to output.</param>
/// <returns></returns>
public static string Humanize(this TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo culture = null, TimeUnit maxUnit = TimeUnit.Week)
public static string Humanize(this TimeSpan timeSpan, int precision, bool countEmptyUnits, CultureInfo culture = null, TimeUnit maxUnit = TimeUnit.Week, TimeUnit minUnit = TimeUnit.Millisecond)
{
IEnumerable<string> timeParts = CreateTheTimePartsWithUperAndLowerLimits(timeSpan, culture, maxUnit, minUnit);
timeParts = SetPrecisionOfTimeSpan(timeParts, precision, countEmptyUnits);

return ConcatenateTimeSpanParts(timeParts);
}

private static IEnumerable<string> CreateTheTimePartsWithUperAndLowerLimits(TimeSpan timespan, CultureInfo culture, TimeUnit maxUnit, TimeUnit minUnit)
{
var cultureFormatter = Configurator.GetFormatter(culture);
bool firstValueFound = false;
IEnumerable<TimeUnit> timeUnitsEnumTypes = GetEnumTypesForTimeUnit();
List<string> timeParts = new List<string>();

foreach (var timeUnitType in timeUnitsEnumTypes)
{
var timepart = GetTimeUnitPart(timeUnitType, timespan, culture, maxUnit, minUnit, cultureFormatter);

if (timepart != null || firstValueFound)
{
firstValueFound = true;
timeParts.Add(timepart);
}
}
if (IsContainingOnlyNullValue(timeParts))
{
string noTimeValueCultureFarmated = cultureFormatter.TimeSpanHumanize_Zero();
timeParts = CreateTimePartsWithNoTimeValue(noTimeValueCultureFarmated);
}
return timeParts;
}

private static IEnumerable<TimeUnit> GetEnumTypesForTimeUnit()
{
IEnumerable<TimeUnit> enumTypeEnumerator = (IEnumerable<TimeUnit>)Enum.GetValues(typeof(TimeUnit));
enumTypeEnumerator = enumTypeEnumerator.Take(_lastTimeUnitTypeIndexImplemented + 1);

return enumTypeEnumerator.Reverse();
}

private static string GetTimeUnitPart(TimeUnit timeUnitToGet, TimeSpan timespan, CultureInfo culture, TimeUnit maximumTimeUnit, TimeUnit minimumTimeUnit, IFormatter cultureFormatter)
{
if(timeUnitToGet <= maximumTimeUnit && timeUnitToGet >= minimumTimeUnit)
{
var isTimeUnitToGetTheMaximumTimeUnit = (timeUnitToGet == maximumTimeUnit);
var numberOfTimeUnits = GetTimeUnitNumericalValue(timeUnitToGet, timespan, isTimeUnitToGetTheMaximumTimeUnit);
return BuildFormatTimePart(cultureFormatter, timeUnitToGet, numberOfTimeUnits);
}
return null;
}

private static int GetTimeUnitNumericalValue(TimeUnit timeUnitToGet, TimeSpan timespan, bool isTimeUnitToGetTheMaximumTimeUnit)
{
switch (timeUnitToGet)
{
case TimeUnit.Millisecond:
return GetNormalCaseTimeAsInteger(timespan.Milliseconds, timespan.TotalMilliseconds, isTimeUnitToGetTheMaximumTimeUnit);
case TimeUnit.Second:
return GetNormalCaseTimeAsInteger(timespan.Seconds, timespan.TotalSeconds, isTimeUnitToGetTheMaximumTimeUnit);
case TimeUnit.Minute:
return GetNormalCaseTimeAsInteger(timespan.Minutes, timespan.TotalMinutes, isTimeUnitToGetTheMaximumTimeUnit);
case TimeUnit.Hour:
return GetNormalCaseTimeAsInteger(timespan.Hours, timespan.TotalHours, isTimeUnitToGetTheMaximumTimeUnit);
case TimeUnit.Day:
return GetSpecialCaseDaysAsInteger(timespan, isTimeUnitToGetTheMaximumTimeUnit);
case TimeUnit.Week:
return GetSpecialCaseWeeksAsInteger(timespan, isTimeUnitToGetTheMaximumTimeUnit);
case TimeUnit.Month:
// To be implemented
case TimeUnit.Year:
// To be implemented
default:
return 0;
}
}

private static int GetSpecialCaseWeeksAsInteger(TimeSpan timespan, bool isTimeUnitToGetTheMaximumTimeUnit)
{
if (isTimeUnitToGetTheMaximumTimeUnit)
{
return timespan.Days / _daysInAWeek;
}
// To be implemented with the implementation of Month and Year
return 0;
}

private static int GetSpecialCaseDaysAsInteger(TimeSpan timespan, bool isTimeUnitToGetTheMaximumTimeUnit)
{
if (isTimeUnitToGetTheMaximumTimeUnit)
{
return timespan.Days;
}
return timespan.Days % _daysInAWeek;
}

private static int GetNormalCaseTimeAsInteger(int timeNumberOfUnits, double totalTimeNumberOfUnits, bool isTimeUnitToGetTheMaximumTimeUnit)
{
if (isTimeUnitToGetTheMaximumTimeUnit)
{
try
{
return Convert.ToInt32(totalTimeNumberOfUnits);
}
catch
{
//To be implemented so that TimeSpanHumanize method accepts double type as unit
return 0;
}
}
return timeNumberOfUnits;
}

private static string BuildFormatTimePart(IFormatter cultureFormatter, TimeUnit timeUnitType, int amountOfTimeUnits)
{
return amountOfTimeUnits != 0
? cultureFormatter.TimeSpanHumanize(timeUnitType, amountOfTimeUnits)
: null;
}

private static List<string> CreateTimePartsWithNoTimeValue(string noTimeValue)
{
return new List<string>() { noTimeValue };
}

private static bool IsContainingOnlyNullValue(IEnumerable<string> timeParts)
{
return (timeParts.Count(x => x != null) == 0);
}

private static IEnumerable<string> SetPrecisionOfTimeSpan(IEnumerable<string> timeParts, int precision, bool countEmptyUnits)
{
var timeParts = GetTimeParts(timeSpan, culture, maxUnit);
if (!countEmptyUnits)
timeParts = timeParts.Where(x => x != null);
timeParts = timeParts.Take(precision);
if (countEmptyUnits)
timeParts = timeParts.Where(x => x != null);
return string.Join(", ", timeParts);
}

private static IEnumerable<string> GetTimeParts(TimeSpan timespan, CultureInfo culture, TimeUnit maxUnit)
{
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 && 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, days);
if (outputHours)
yield return GetTimePart(formatter, TimeUnit.Hour, hours);
if (outputMinutes)
yield return GetTimePart(formatter, TimeUnit.Minute, minutes);
if (outputSeconds)
yield return GetTimePart(formatter, TimeUnit.Second, seconds);
if (outputMilliseconds)
yield return GetTimePart(formatter, TimeUnit.Millisecond, milliseconds);
else
yield return formatter.TimeSpanHumanize_Zero();
return timeParts;
}

private static string GetTimePart(IFormatter formatter, TimeUnit timeUnit, int unit)
private static string ConcatenateTimeSpanParts(IEnumerable<string> timeSpanParts)
{
return unit != 0
? formatter.TimeSpanHumanize(timeUnit, unit)
: null;
return string.Join(", ", timeSpanParts);
}
}
}

0 comments on commit b01cb7d

Please sign in to comment.