diff --git a/src/Humanizer.Tests/DateHumanizeTests.cs b/src/Humanizer.Tests/DateHumanizeTests.cs index adfe50d74..7a05c6beb 100644 --- a/src/Humanizer.Tests/DateHumanizeTests.cs +++ b/src/Humanizer.Tests/DateHumanizeTests.cs @@ -8,11 +8,8 @@ public class DateHumanizeTests { static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) { - var utcNow = DateTime.UtcNow; - var localNow = DateTime.Now; - - Assert.Equal(expectedString, utcNow.Add(deltaFromNow).Humanize()); - Assert.Equal(expectedString, localNow.Add(deltaFromNow).Humanize(false)); + Assert.Equal(expectedString, DateTime.UtcNow.Add(deltaFromNow).Humanize()); + Assert.Equal(expectedString, DateTime.Now.Add(deltaFromNow).Humanize(false)); } static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow) diff --git a/src/Humanizer.Tests/Humanizer.Tests.csproj b/src/Humanizer.Tests/Humanizer.Tests.csproj index 088335551..45f2f40d5 100644 --- a/src/Humanizer.Tests/Humanizer.Tests.csproj +++ b/src/Humanizer.Tests/Humanizer.Tests.csproj @@ -71,6 +71,7 @@ + @@ -79,6 +80,7 @@ + diff --git a/src/Humanizer.Tests/Localisation/DynamicResourceKeys/DateHumanizeTests.cs b/src/Humanizer.Tests/Localisation/DynamicResourceKeys/DateHumanizeTests.cs new file mode 100644 index 000000000..059152477 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/DynamicResourceKeys/DateHumanizeTests.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Humanizer.Localisation.DynamicResourceKeys; +using Xunit; +using Xunit.Extensions; +using Resources = Humanizer.Localisation.Resources; +using ResourceKeys = Humanizer.Localisation.DynamicResourceKeys.ResourceKeys; +using Dyna = Humanizer.DynamicResourceKeys.DateHumanizeExtensions; + +namespace Humanizer.Tests.Localisation.DynamicResourceKeys +{ + public class DateHumanizeWithResourceKeysTests + { + static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow) + { + Assert.Equal(expectedString, Dyna.Humanize(DateTime.UtcNow.Add(deltaFromNow))); + Assert.Equal(expectedString, Dyna.Humanize(DateTime.Now.Add(deltaFromNow), false)); + } + + 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); + + Assert.Equal(expectedString, Dyna.Humanize(utcNow.Add(deltaFromNow), true, utcNow)); + Assert.Equal(expectedString, Dyna.Humanize(now.Add(deltaFromNow), false, now)); + } + + static void Verify(string expectedString, TimeSpan deltaFromNow) + { + VerifyWithCurrentDate(expectedString, deltaFromNow); + VerifyWithDateInjection(expectedString, deltaFromNow); + } + + public static IEnumerable OneTimeUnitAgoTestsSource + { + get + { + return new[] { + new object[]{ TimeUnit.Second, TimeSpan.FromSeconds(-1) }, + new object[]{ TimeUnit.Minute, TimeSpan.FromMinutes(-1) }, + new object[]{ TimeUnit.Hour, TimeSpan.FromHours(-1) }, + new object[]{ TimeUnit.Day, TimeSpan.FromDays(-1) }, + new object[]{ TimeUnit.Month, TimeSpan.FromDays(-30) }, + new object[]{ TimeUnit.Year, TimeSpan.FromDays(-365) }, + }; + } + } + + [Theory] + [PropertyData("OneTimeUnitAgoTestsSource")] + public void OneTimeUnitAgo(TimeUnit unit, TimeSpan timeSpan) + { + Verify(Resources.GetResource(ResourceKeys.DateHumanize.GetResourceKey(unit, 1)), timeSpan); + } + } +} \ No newline at end of file diff --git a/src/Humanizer.Tests/Localisation/DynamicResourceKeys/ResourceKeyTests.cs b/src/Humanizer.Tests/Localisation/DynamicResourceKeys/ResourceKeyTests.cs new file mode 100644 index 000000000..e2bb9b2d4 --- /dev/null +++ b/src/Humanizer.Tests/Localisation/DynamicResourceKeys/ResourceKeyTests.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; +using Humanizer.Localisation.DynamicResourceKeys; +using Xunit; +using Xunit.Extensions; +using Resources = Humanizer.Localisation.Resources; + +namespace Humanizer.Tests.Localisation.DynamicResourceKeys +{ + public class ResourceKeyTests + { + [Theory] + [PropertyData("DateHumanizeResourceKeys")] + public void DateHumanizeKeysGeneration(string expected, string actual) + { + Assert.Equal(expected, actual); + } + + [Theory] + [PropertyData("TimeSpanHumanizeResourceKeys")] + public void TimeSpanHumanizeKeysGeneration(string expected, string actual) + { + Assert.Equal(expected, actual); + } + + [Theory] + [PropertyData("DateHumanizeResourceKeys")] + public void DateHumanizeKeysExistence(string expectedResourceKey, string generatedResourceKey) + { + Assert.NotNull(Resources.GetResource(generatedResourceKey)); + } + + [Theory] + [PropertyData("TimeSpanHumanizeResourceKeys")] + public void TimeSpanHumanizeKeysExistence(string expectedResourceKey, string generatedResourceKey) + { + Assert.NotNull(Resources.GetResource(generatedResourceKey)); + } + + public static IEnumerable DateHumanizeResourceKeys + { + get + { + return new[] { + new object[]{ "DateHumanize_SingleSecondAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Second, 1) }, + new object[]{ "DateHumanize_SingleMinuteAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Minute, 1) }, + new object[]{ "DateHumanize_SingleHourAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Hour, 1) }, + new object[]{ "DateHumanize_SingleDayAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Day, 1) }, + new object[]{ "DateHumanize_SingleMonthAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Month, 1) }, + new object[]{ "DateHumanize_SingleYearAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Year, 1) }, + new object[]{ "DateHumanize_MultipleSecondsAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Second, 10) }, + new object[]{ "DateHumanize_MultipleMinutesAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Minute, 10) }, + new object[]{ "DateHumanize_MultipleHoursAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Hour, 10) }, + new object[]{ "DateHumanize_MultipleDaysAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Day, 10) }, + new object[]{ "DateHumanize_MultipleMonthsAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Month, 10) }, + new object[]{ "DateHumanize_MultipleYearsAgo", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Year, 10) }, + + new object[]{ "DateHumanize_SingleSecondFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Second, 1, true) }, + new object[]{ "DateHumanize_SingleMinuteFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Minute, 1, true) }, + new object[]{ "DateHumanize_SingleHourFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Hour, 1, true) }, + new object[]{ "DateHumanize_SingleDayFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Day, 1, true) }, + new object[]{ "DateHumanize_SingleMonthFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Month, 1, true) }, + new object[]{ "DateHumanize_SingleYearFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Year, 1, true) }, + new object[]{ "DateHumanize_MultipleSecondsFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Second, 10, true) }, + new object[]{ "DateHumanize_MultipleMinutesFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Minute, 10, true) }, + new object[]{ "DateHumanize_MultipleHoursFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Hour, 10, true) }, + new object[]{ "DateHumanize_MultipleDaysFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Day, 10, true) }, + new object[]{ "DateHumanize_MultipleMonthsFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Month, 10, true) }, + new object[]{ "DateHumanize_MultipleYearsFromNow", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Year, 10, true) }, + + new object[]{ "DateHumanize_Now", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Millisecond, 0) }, + new object[]{ "DateHumanize_Now", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Second, 0) }, + new object[]{ "DateHumanize_Now", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Minute, 0) }, + new object[]{ "DateHumanize_Now", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Hour, 0) }, + new object[]{ "DateHumanize_Now", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Day, 0) }, + new object[]{ "DateHumanize_Now", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Week, 0) }, + new object[]{ "DateHumanize_Now", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Month, 0) }, + new object[]{ "DateHumanize_Now", ResourceKeys.DateHumanize.GetResourceKey(TimeUnit.Year, 0) } + }; + } + } + + public static IEnumerable TimeSpanHumanizeResourceKeys + { + get + { + return new[] { + new object[]{ "TimeSpanHumanize_SingleSecond", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Second, 1) }, + new object[]{ "TimeSpanHumanize_SingleMinute", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Minute, 1) }, + new object[]{ "TimeSpanHumanize_SingleHour", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Hour, 1) }, + new object[]{ "TimeSpanHumanize_SingleDay", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Day, 1) }, + new object[]{ "TimeSpanHumanize_SingleWeek", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Week, 1) }, + new object[]{ "TimeSpanHumanize_MultipleSeconds", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Second, 10) }, + new object[]{ "TimeSpanHumanize_MultipleMinutes", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Minute, 10) }, + new object[]{ "TimeSpanHumanize_MultipleHours", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Hour, 10) }, + new object[]{ "TimeSpanHumanize_MultipleDays", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Day, 10) }, + new object[]{ "TimeSpanHumanize_MultipleWeeks", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Week, 10) }, + + new object[]{ "TimeSpanHumanize_Zero", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Millisecond, 0) }, + new object[]{ "TimeSpanHumanize_Zero", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Second, 0) }, + new object[]{ "TimeSpanHumanize_Zero", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Minute, 0) }, + new object[]{ "TimeSpanHumanize_Zero", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Hour, 0) }, + new object[]{ "TimeSpanHumanize_Zero", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Day, 0) }, + new object[]{ "TimeSpanHumanize_Zero", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Week, 0) }, + new object[]{ "TimeSpanHumanize_Zero", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Month, 0) }, + new object[]{ "TimeSpanHumanize_Zero", ResourceKeys.TimeSpanHumanize.GetResourceKey(TimeUnit.Year, 0) } + }; + } + } + } +} diff --git a/src/Humanizer/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs index 463f8e889..c1bc79e47 100644 --- a/src/Humanizer/Configuration/Configurator.cs +++ b/src/Humanizer/Configuration/Configurator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using Humanizer.Localisation; +using Dyna = Humanizer.Localisation.DynamicResourceKeys; namespace Humanizer.Configuration { @@ -33,5 +34,16 @@ public static IFormatter Formatter return new DefaultFormatter(); } } + + /// + /// Providers similar functionality for the DefaultFormatter except the localization, at the moment. + /// + public static Dyna.IFormatter DynamicFormatter + { + get + { + return new Dyna.DefaultFormatter(); + } + } } } diff --git a/src/Humanizer/DynamicResourceKeys/DateHumanizeExtensions.cs b/src/Humanizer/DynamicResourceKeys/DateHumanizeExtensions.cs new file mode 100644 index 000000000..933f48a6d --- /dev/null +++ b/src/Humanizer/DynamicResourceKeys/DateHumanizeExtensions.cs @@ -0,0 +1,76 @@ +using System; +using Humanizer.Configuration; + +namespace Humanizer.DynamicResourceKeys +{ + /// + /// Humanizes DateTime into human readable sentence + /// + public static class DateHumanizeExtensions + { + // http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time + /// + /// Turns the current or provided date into a human readable sentence + /// + /// 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 + /// + public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null) + { + if (dateToCompareAgainst == null) + dateToCompareAgainst = DateTime.UtcNow; + + var formatter = Configurator.DynamicFormatter; + var comparisonBase = dateToCompareAgainst.Value; + + if (!utcDate) + comparisonBase = comparisonBase.ToLocalTime(); + + if (input <= comparisonBase && comparisonBase.Subtract(input) < TimeSpan.FromMilliseconds(500)) + return formatter.DateHumanize_Now(); + + var isFuture = input > comparisonBase; + var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); + + if (ts.TotalSeconds < 60) + return formatter.DateHumanize_Seconds(ts.Seconds, isFuture); + + if (ts.TotalSeconds < 120) + return formatter.DateHumanize_Minutes(1, isFuture); + + if (ts.TotalMinutes < 45) + return formatter.DateHumanize_Minutes(ts.Minutes, isFuture); + + if (ts.TotalMinutes < 90) + return formatter.DateHumanize_Hours(1, isFuture); + + if (ts.TotalHours < 24) + return formatter.DateHumanize_Hours(ts.Hours, isFuture); + + if (ts.TotalHours < 48) + return formatter.DateHumanize_Days(1, isFuture); + + if (ts.TotalDays < 28) + return formatter.DateHumanize_Days(ts.Days, isFuture); + + if (ts.TotalDays >= 28 && ts.TotalDays < 30) + { + if (comparisonBase.Date.AddMonths(isFuture ? 1 : -1) == input.Date) + return formatter.DateHumanize_Months(1, isFuture); + + return formatter.DateHumanize_Days(ts.Days, isFuture); + } + + if (ts.TotalDays < 345) + { + int months = Convert.ToInt32(Math.Floor(ts.TotalDays / 29.5)); + return formatter.DateHumanize_Months(months, isFuture); + } + + int years = Convert.ToInt32(Math.Floor(ts.TotalDays / 365)); + if (years == 0) years = 1; + return formatter.DateHumanize_Years(years, isFuture); + } + } +} \ No newline at end of file diff --git a/src/Humanizer/DynamicResourceKeys/TimeSpanHumanizeExtensions.cs b/src/Humanizer/DynamicResourceKeys/TimeSpanHumanizeExtensions.cs new file mode 100644 index 000000000..5e184f385 --- /dev/null +++ b/src/Humanizer/DynamicResourceKeys/TimeSpanHumanizeExtensions.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Humanizer.Configuration; + +namespace Humanizer.DynamicResourceKeys +{ + /// + /// Humanizes TimeSpan into human readable form + /// + public static class TimeSpanHumanizeExtensions + { + /// + /// Turns a TimeSpan into a human readable form. E.g. 1 day. + /// + /// + /// The maximum number of time units to return. Defaulted is 1 which means the largest unit is returned + /// + public static string Humanize(this TimeSpan timeSpan, int precision = 1) + { + var result = new StringBuilder(); + for (int i = 0; i < precision; i++) + { + var timePart = FormatParameters + .Select(format => TryFormat(format, timeSpan)) + .FirstOrDefault(part => part != null); + + if (result.Length > 0) + result.Append(", "); + + result.Append(timePart); + + timeSpan = TakeOutTheLargestUnit(timeSpan); + if (timeSpan == TimeSpan.Zero) + break; + } + + return result.ToString(); + } + + static TimeSpan TakeOutTheLargestUnit(TimeSpan timeSpan) + { + return timeSpan - LargestUnit(timeSpan); + } + + static TimeSpan LargestUnit(TimeSpan timeSpan) + { + var days = timeSpan.Days; + if (days >= 7) + return TimeSpan.FromDays((days/7) * 7); + if (days >= 1) + return TimeSpan.FromDays(days); + + var hours = timeSpan.Hours; + if (hours >= 1) + return TimeSpan.FromHours(hours); + + var minutes = timeSpan.Minutes; + if (minutes >= 1) + return TimeSpan.FromMinutes(minutes); + + var seconds = timeSpan.Seconds; + if (seconds >= 1) + return TimeSpan.FromSeconds(seconds); + + var milliseconds = timeSpan.Milliseconds; + if (milliseconds >= 1) + return TimeSpan.FromMilliseconds(milliseconds); + + return TimeSpan.Zero; + } + + /// + /// Gets the elements of the TimeSpan associated with their correct formatter methods + /// in zero, single and multiple forms. + /// + static IEnumerable FormatParameters + { + get + { + var formatter = Configurator.DynamicFormatter; + + return new[] + { + new TimeSpanPropertyFormat( + timespan => timespan.Days / 7, + formatter.TimeSpanHumanize_Weeks), + new TimeSpanPropertyFormat( + timespan => timespan.Days, + formatter.TimeSpanHumanize_Days), + new TimeSpanPropertyFormat( + timespan => timespan.Hours, + formatter.TimeSpanHumanize_Hours), + new TimeSpanPropertyFormat( + timespan => timespan.Minutes, + formatter.TimeSpanHumanize_Minutes), + new TimeSpanPropertyFormat( + timespan => timespan.Seconds, + formatter.TimeSpanHumanize_Seconds), + new TimeSpanPropertyFormat( + timespan => timespan.Milliseconds, + formatter.TimeSpanHumanize_Milliseconds), + new TimeSpanPropertyFormat( + timespan => 0, + formatter.TimeSpanHumanize_Zero) + }; + } + } + + /// + /// Maps a single property (Day, Hour etc.) of TimeSpan to a formatted string "1 day" etc. + /// + /// + /// + /// + private static string TryFormat( + TimeSpanPropertyFormat propertyFormat, + TimeSpan timeSpan) + { + var value = propertyFormat.PropertySelector(timeSpan); + switch (value) + { + case 0: + return propertyFormat.Zero(); + default: + return propertyFormat.Format(value); + } + } + + /// + /// Stores a single mapping of a part of the time span (Day, Hour etc.) to its associated + /// formatter method for Zero, Single, Multiple. + /// + class TimeSpanPropertyFormat + { + public TimeSpanPropertyFormat( + Func propertySelector, + Func format) + { + PropertySelector = propertySelector; + Format = format; + Zero = () => null; + } + + public TimeSpanPropertyFormat(Func propertySelector, Func zeroFunc) + { + PropertySelector = propertySelector; + Zero = zeroFunc; + } + + public Func PropertySelector { get; private set; } + public Func Format { get; private set; } + public Func Zero { get; private set; } + } + } +} \ No newline at end of file diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj index aa7c4f1a3..7ffda03ef 100644 --- a/src/Humanizer/Humanizer.csproj +++ b/src/Humanizer/Humanizer.csproj @@ -72,6 +72,8 @@ + + True True @@ -83,9 +85,16 @@ On.Days.tt + + + + + + + diff --git a/src/Humanizer/Localisation/DefaultFormatter.cs b/src/Humanizer/Localisation/DefaultFormatter.cs index 02d95477c..a88cd1c57 100644 --- a/src/Humanizer/Localisation/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/DefaultFormatter.cs @@ -199,7 +199,7 @@ protected virtual string Format(string resourceKey) protected virtual string Format(string resourceKey, int number) { - return string.Format(Resources.GetResource(GetResourceKey(resourceKey, number)), number); + return Resources.GetResource(GetResourceKey(resourceKey, number)).FormatWith(number); } protected virtual string GetResourceKey(string resourceKey, int number) diff --git a/src/Humanizer/Localisation/DynamicResourceKeys/DefaultFormatter.cs b/src/Humanizer/Localisation/DynamicResourceKeys/DefaultFormatter.cs new file mode 100644 index 000000000..b9fad2b84 --- /dev/null +++ b/src/Humanizer/Localisation/DynamicResourceKeys/DefaultFormatter.cs @@ -0,0 +1,184 @@ +namespace Humanizer.Localisation.DynamicResourceKeys +{ + /// + /// Default implementation of IFormatter interface. + /// + public class DefaultFormatter : IFormatter + { + /// + /// Now! + /// + /// Time expressed in words + public virtual string DateHumanize_Now() + { + return GetResourceForDate(TimeUnit.Millisecond, 0); + } + + /// + /// To express time in secounds. + /// + /// number of seconds + /// boolean flag, is it in future? + /// Time expressed in words + public virtual string DateHumanize_Seconds(int seconds = 1, bool isFuture = false) + { + return GetResourceForDate(TimeUnit.Second, seconds, isFuture); + } + + /// + /// To express time in minutes. + /// + /// number of minutes + /// boolean flag, is it in future? + /// Time expressed in words + public virtual string DateHumanize_Minutes(int minutes = 1, bool isFuture = false) + { + return GetResourceForDate(TimeUnit.Minute, minutes, isFuture); + } + + /// + /// To express time in hours. + /// + /// number of hours + /// boolean flag, is it in future? + /// Time expressed in words + public virtual string DateHumanize_Hours(int hours = 1, bool isFuture = false) + { + return GetResourceForDate(TimeUnit.Hour, hours, isFuture); + } + + /// + /// To express time in days. + /// + /// number of days + /// boolean flag, is it in future? + /// Time expressed in words + public virtual string DateHumanize_Days(int days = 1, bool isFuture = false) + { + return GetResourceForDate(TimeUnit.Day, days, isFuture); + } + + /// + /// To express time in months + /// + /// number of months + /// boolean flag, is it in future? + /// Time expressed in words + public virtual string DateHumanize_Months(int months = 1, bool isFuture = false) + { + return GetResourceForDate(TimeUnit.Month, months, isFuture); + } + + /// + /// To express time in years + /// + /// number of years + /// boolean flag, is it in future? + /// Time expressed in words + public virtual string DateHumanize_Years(int years = 1, bool isFuture = false) + { + return GetResourceForDate(TimeUnit.Year, years, isFuture); + } + + /// + /// In NO time! + /// + /// Time expressed in words + public virtual string TimeSpanHumanize_Zero() + { + return GetResourceForTimeSpan(TimeUnit.Millisecond, 0); + } + + /// + /// To express time in milliseconds. + /// + /// number of milliseconds + /// Time expressed in words + public virtual string TimeSpanHumanize_Milliseconds(int milliSeconds = 1) + { + return GetResourceForTimeSpan(TimeUnit.Millisecond, milliSeconds); + } + + /// + /// To express time in secounds. + /// + /// number of seconds + /// Time expressed in words + public virtual string TimeSpanHumanize_Seconds(int seconds = 1) + { + return GetResourceForTimeSpan(TimeUnit.Second, seconds); + } + + /// + /// To express time in minutes. + /// + /// number of minutes + /// Time expressed in words + public virtual string TimeSpanHumanize_Minutes(int minutes = 1) + { + return GetResourceForTimeSpan(TimeUnit.Minute, minutes); + } + + /// + /// To express time in hours + /// + /// number of hours + /// Time expressed in words + public virtual string TimeSpanHumanize_Hours(int hours = 1) + { + return GetResourceForTimeSpan(TimeUnit.Hour, hours); + } + + /// + /// To express time in days. + /// + /// number of days + /// Time expressed in words + public virtual string TimeSpanHumanize_Days(int days = 1) + { + return GetResourceForTimeSpan(TimeUnit.Day, days); + } + + /// + /// To express time in weeks. + /// + /// number of weeks + /// Time expressed in words + public virtual string TimeSpanHumanize_Weeks(int weeks = 1) + { + return GetResourceForTimeSpan(TimeUnit.Week, weeks); + } + + private string GetResourceForDate(TimeUnit unit, int count, bool isFuture = false) + { + string resourceKey = ResourceKeys.DateHumanize.GetResourceKey(unit, count, isFuture); + return count == 1 ? Format(resourceKey) : Format(resourceKey, count); + } + + private string GetResourceForTimeSpan(TimeUnit unit, int count) + { + string resourceKey = ResourceKeys.TimeSpanHumanize.GetResourceKey(unit, count); + return count == 1 ? Format(resourceKey) : Format(resourceKey, count); + } + + protected virtual string Format(string resourceKey) + { + return Resources.GetResource(GetResourceKey(resourceKey)); + } + + protected virtual string Format(string resourceKey, int number) + { + return Resources.GetResource(GetResourceKey(resourceKey, number)).FormatWith(number); + } + + protected virtual string GetResourceKey(string resourceKey, int number) + { + return resourceKey; + } + + protected virtual string GetResourceKey(string resourceKey) + { + return resourceKey; + } + } +} \ No newline at end of file diff --git a/src/Humanizer/Localisation/DynamicResourceKeys/IFormatter.cs b/src/Humanizer/Localisation/DynamicResourceKeys/IFormatter.cs new file mode 100644 index 000000000..844969f9b --- /dev/null +++ b/src/Humanizer/Localisation/DynamicResourceKeys/IFormatter.cs @@ -0,0 +1,26 @@ +namespace Humanizer.Localisation.DynamicResourceKeys +{ + /// + /// Implement this interface if your language has complex rules around dealing with numbers. + /// For example in Romanian "5 days" is "5 zile", while "24 days" is "24 de zile" and + /// in Arabic 2 days is يومين not 2 يوم + /// + public interface IFormatter + { + string DateHumanize_Now(); + string DateHumanize_Seconds(int seconds = 1, bool isFuture = false); + string DateHumanize_Minutes(int minutes = 1, bool isFuture = false); + string DateHumanize_Hours(int hours = 1, bool isFuture = false); + string DateHumanize_Days(int days = 1, bool isFuture = false); + string DateHumanize_Months(int months = 1, bool isFuture = false); + string DateHumanize_Years(int years = 1, bool isFuture = false); + + string TimeSpanHumanize_Zero(); + string TimeSpanHumanize_Milliseconds(int milliSeconds = 1); + string TimeSpanHumanize_Seconds(int seconds = 1); + string TimeSpanHumanize_Minutes(int minutes = 1); + string TimeSpanHumanize_Hours(int hours = 1); + string TimeSpanHumanize_Days(int days = 1); + string TimeSpanHumanize_Weeks(int weeks = 1); + } +} diff --git a/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.Common.cs b/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.Common.cs new file mode 100644 index 000000000..8ead3df59 --- /dev/null +++ b/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.Common.cs @@ -0,0 +1,15 @@ +using System; + +namespace Humanizer.Localisation.DynamicResourceKeys +{ + public partial class ResourceKeys + { + private const string Single = "Single"; + private const string Multiple = "Multiple"; + + private static void ValidateRange(int count) + { + if (count < 1) throw new ArgumentOutOfRangeException("count"); + } + } +} diff --git a/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.DateHumanize.cs b/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.DateHumanize.cs new file mode 100644 index 000000000..7dee6d2de --- /dev/null +++ b/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.DateHumanize.cs @@ -0,0 +1,27 @@ +using System; + +namespace Humanizer.Localisation.DynamicResourceKeys +{ + public partial class ResourceKeys + { + public static class DateHumanize + { + /// + /// Examples: DateHumanize_SingleMinuteAgo, DateHumanize_MultipleHoursAgo + /// Note: "s" for plural served separately by third part. + /// + private const string DateTimeFormat = "DateHumanize_{0}{1}{2}{3}"; + private const string Now = "DateHumanize_Now"; + private const string Ago = "Ago"; + private const string FromNow = "FromNow"; + + public static string GetResourceKey(TimeUnit unit, int count, bool isFuture = false) + { + if (count == 0) return Now; + + ValidateRange(count); + return DateTimeFormat.FormatWith(count == 1 ? Single : Multiple, unit, count == 1 ? "" : "s", isFuture ? FromNow : Ago); + } + } + } +} diff --git a/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.TimeSpanHumanize.cs b/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.TimeSpanHumanize.cs new file mode 100644 index 000000000..a820c24a5 --- /dev/null +++ b/src/Humanizer/Localisation/DynamicResourceKeys/ResourceKeys.TimeSpanHumanize.cs @@ -0,0 +1,25 @@ +using System; + +namespace Humanizer.Localisation.DynamicResourceKeys +{ + public partial class ResourceKeys + { + public static class TimeSpanHumanize + { + /// + /// Examples: TimeSpanHumanize_SingleMinute, TimeSpanHumanize_MultipleHours. + /// Note: "s" for plural served separately by third part. + /// + private const string TimeSpanFormat = "TimeSpanHumanize_{0}{1}{2}"; + private const string Zero = "TimeSpanHumanize_Zero"; + + public static string GetResourceKey(TimeUnit unit, int count) + { + if (count == 0) return Zero; + + ValidateRange(count); + return TimeSpanFormat.FormatWith(count == 1 ? Single : Multiple, unit, count == 1 ? "" : "s"); + } + } + } +} diff --git a/src/Humanizer/Localisation/DynamicResourceKeys/TimeUnit.cs b/src/Humanizer/Localisation/DynamicResourceKeys/TimeUnit.cs new file mode 100644 index 000000000..6ab8c7b17 --- /dev/null +++ b/src/Humanizer/Localisation/DynamicResourceKeys/TimeUnit.cs @@ -0,0 +1,17 @@ +namespace Humanizer.Localisation.DynamicResourceKeys +{ + /// + /// Units of time. + /// + public enum TimeUnit + { + Millisecond, + Second, + Minute, + Hour, + Day, + Week, + Month, + Year + } +} \ No newline at end of file diff --git a/src/Humanizer/StringExentions.cs b/src/Humanizer/StringExentions.cs new file mode 100644 index 000000000..9b557d7e5 --- /dev/null +++ b/src/Humanizer/StringExentions.cs @@ -0,0 +1,21 @@ +using System; + +namespace Humanizer +{ + /// + /// Extension methods for String type. + /// + public static class StringExentions + { + /// + /// Extension method to format string with passed arguments + /// + /// string format + /// arguments + /// + public static string FormatWith(this string format, params object[] args) + { + return String.Format(format, args); + } + } +}