diff --git a/src/Humanizer.Tests.Shared/DateHumanize.cs b/src/Humanizer.Tests.Shared/DateHumanize.cs index edfd67c1a..78f40b4b2 100644 --- a/src/Humanizer.Tests.Shared/DateHumanize.cs +++ b/src/Humanizer.Tests.Shared/DateHumanize.cs @@ -11,30 +11,30 @@ public class DateHumanize { private static readonly object LockObject = new object(); - private static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow, CultureInfo culture) + private static void VerifyWithCurrentDate(string expectedString, TimeSpan deltaFromNow, CultureInfo culture, DateTimeExpressionProvider dateTimeExpressionProvider) { 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 - VerifyWithDate(expectedString, deltaFromNow, culture, localNow, utcNow); + VerifyWithDate(expectedString, deltaFromNow, culture, localNow, utcNow, dateTimeExpressionProvider); } - private static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow, CultureInfo culture) + private static void VerifyWithDateInjection(string expectedString, TimeSpan deltaFromNow, CultureInfo culture, DateTimeExpressionProvider dateTimeExpressionProvider) { var utcNow = new DateTime(2013, 6, 20, 9, 58, 22, DateTimeKind.Utc); var now = new DateTime(2013, 6, 20, 11, 58, 22, DateTimeKind.Local); - VerifyWithDate(expectedString, deltaFromNow, culture, now, utcNow); + VerifyWithDate(expectedString, deltaFromNow, culture, now, utcNow, dateTimeExpressionProvider); } - private static void VerifyWithDate(string expectedString, TimeSpan deltaFromBase, CultureInfo culture, DateTime baseDate, DateTime baseDateUtc) + private static void VerifyWithDate(string expectedString, TimeSpan deltaFromBase, CultureInfo culture, DateTime baseDate, DateTime baseDateUtc, DateTimeExpressionProvider dateTimeExpressionProvider) { - Assert.Equal(expectedString, baseDateUtc.Add(deltaFromBase).Humanize(utcDate: true, dateToCompareAgainst: baseDateUtc, culture: culture)); - Assert.Equal(expectedString, baseDate.Add(deltaFromBase).Humanize(false, baseDate, culture: culture)); + Assert.Equal(expectedString, baseDateUtc.Add(deltaFromBase).Humanize(utcDate: true, dateToCompareAgainst: baseDateUtc, culture: culture, dateTimeExpressionProvider)); + Assert.Equal(expectedString, baseDate.Add(deltaFromBase).Humanize(false, baseDate, culture: culture, dateTimeExpressionProvider)); } - public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null, CultureInfo culture = null, DateTime? baseDate = null, DateTime? baseDateUtc = null) + public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Tense tense, double? precision = null, CultureInfo culture = null, DateTime? baseDate = null, DateTime? baseDateUtc = null, DateTimeExpressionProvider dateTimeExpressionProvider = null) { // We lock this as these tests can be multi-threaded and we're setting a static lock (LockObject) @@ -83,14 +83,14 @@ public static void Verify(string expectedString, int unit, TimeUnit timeUnit, Te if (baseDate == null) { - VerifyWithCurrentDate(expectedString, deltaFromNow, culture); - VerifyWithDateInjection(expectedString, deltaFromNow, culture); + VerifyWithCurrentDate(expectedString, deltaFromNow, culture, dateTimeExpressionProvider); + VerifyWithDateInjection(expectedString, deltaFromNow, culture, dateTimeExpressionProvider); } else { - VerifyWithDate(expectedString, deltaFromNow, culture, baseDate.Value, baseDateUtc.Value); + VerifyWithDate(expectedString, deltaFromNow, culture, baseDate.Value, baseDateUtc.Value, dateTimeExpressionProvider); } } } } -} \ No newline at end of file +} diff --git a/src/Humanizer.Tests/DateTimeHumanizePrecisionTimeExpressionsTests.cs b/src/Humanizer.Tests/DateTimeHumanizePrecisionTimeExpressionsTests.cs new file mode 100644 index 000000000..1a666a9bb --- /dev/null +++ b/src/Humanizer.Tests/DateTimeHumanizePrecisionTimeExpressionsTests.cs @@ -0,0 +1,192 @@ +using Humanizer.Localisation; +using Xunit; + +namespace Humanizer.Tests +{ + [UseCulture("en-US")] + public class DateTimeHumanizePrecisionTimeExpressionsTests + { + + private const double DefaultPrecision = .75; + private static readonly TimeExpressionProviderToTest _timeExpressionProvider = new TimeExpressionProviderToTest(); + + [Theory] + [InlineData(1, "now")] + [InlineData(749, "now")] + [InlineData(750, "for one second")] + [InlineData(1000, "for one second")] + [InlineData(1749, "for one second")] + [InlineData(1750, "for 2 seconds")] + public void MillisecondsAgo(int milliseconds, string expected) + { + DateHumanize.Verify(expected, milliseconds, TimeUnit.Millisecond, Tense.Past, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "now")] + [InlineData(749, "now")] + [InlineData(750, "in one second")] + [InlineData(1000, "in one second")] + [InlineData(1749, "in one second")] + [InlineData(1750, "in 2 seconds")] + public void MillisecondsFromNow(int milliseconds, string expected) + { + DateHumanize.Verify(expected, milliseconds, TimeUnit.Millisecond, Tense.Future, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "for one second")] + [InlineData(10, "for 10 seconds")] + [InlineData(44, "for 44 seconds")] + [InlineData(45, "for a minute")] + [InlineData(60, "for a minute")] + [InlineData(104, "for a minute")] + [InlineData(105, "for 2 minutes")] + [InlineData(120, "for 2 minutes")] + public void SecondsAgo(int seconds, string expected) + { + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "in one second")] + [InlineData(10, "in 10 seconds")] + [InlineData(44, "in 44 seconds")] + [InlineData(45, "in a minute")] + [InlineData(60, "in a minute")] + [InlineData(104, "in a minute")] + [InlineData(105, "in 2 minutes")] + [InlineData(120, "in 2 minutes")] + public void SecondsFromNow(int seconds, string expected) + { + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Future, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "for a minute")] + [InlineData(10, "for 10 minutes")] + [InlineData(44, "for 44 minutes")] + [InlineData(45, "for an hour")] + [InlineData(60, "for an hour")] + [InlineData(104, "for an hour")] + [InlineData(105, "for 2 hours")] + [InlineData(120, "for 2 hours")] + public void MinutesAgo(int minutes, string expected) + { + DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Past, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "in a minute")] + [InlineData(10, "in 10 minutes")] + [InlineData(44, "in 44 minutes")] + [InlineData(45, "in an hour")] + [InlineData(60, "in an hour")] + [InlineData(104, "in an hour")] + [InlineData(105, "in 2 hours")] + [InlineData(120, "in 2 hours")] + public void MinutesFromNow(int minutes, string expected) + { + DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Future, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "for an hour")] + [InlineData(10, "for 10 hours")] + [InlineData(17, "for 17 hours")] + [InlineData(18, "for one day")] + [InlineData(24, "for one day")] + [InlineData(41, "for one day")] + [InlineData(42, "for 2 days")] + [InlineData(48, "for 2 days")] + [InlineData(60, "for 2 days")] + public void HoursAgo(int hours, string expected) + { + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "in an hour")] + [InlineData(10, "in 10 hours")] + [InlineData(18, "tomorrow")] + [InlineData(24, "tomorrow")] + [InlineData(41, "tomorrow")] + [InlineData(42, "in 2 days")] + [InlineData(48, "in 2 days")] + [InlineData(60, "in 2 days")] + public void HoursFromNow(int hours, string expected) + { + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Future, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "for one day")] + [InlineData(10, "for 10 days")] + [InlineData(20, "for 20 days")] + [InlineData(22, "for 22 days")] + [InlineData(23, "for one month")] + [InlineData(31, "for one month")] + [InlineData(43, "for one month")] + [InlineData(53, "for 2 months")] + public void DaysAgo(int days, string expected) + { + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "tomorrow")] + [InlineData(10, "in 10 days")] + [InlineData(20, "in 20 days")] + [InlineData(22, "in 22 days")] + [InlineData(23, "in one month")] + [InlineData(31, "in one month")] + [InlineData(43, "in one month")] + [InlineData(53, "in 2 months")] + public void DaysFromNow(int days, string expected) + { + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Future, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "for one month")] + [InlineData(8, "for 8 months")] + [InlineData(9, "for one year")] + [InlineData(12, "for one year")] + [InlineData(19, "for one year")] + [InlineData(21, "for 2 years")] + [InlineData(24, "for 2 years")] + public void MonthsAgo(int months, string expected) + { + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "in one month")] + [InlineData(8, "in 8 months")] + [InlineData(9, "in one year")] + [InlineData(12, "in one year")] + [InlineData(19, "in one year")] + [InlineData(21, "in 2 years")] + [InlineData(24, "in 2 years")] + public void MonthsFromNow(int months, string expected) + { + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Future, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "for one year")] + [InlineData(2, "for 2 years")] + public void YearsAgo(int years, string expected) + { + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + + [Theory] + [InlineData(1, "in one year")] + [InlineData(2, "in 2 years")] + public void YearsFromNow(int years, string expected) + { + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Future, DefaultPrecision, dateTimeExpressionProvider: _timeExpressionProvider); + } + } +} diff --git a/src/Humanizer.Tests/DateTimeHumanizeSupportTimeExpressionsTests.cs b/src/Humanizer.Tests/DateTimeHumanizeSupportTimeExpressionsTests.cs new file mode 100644 index 000000000..d74f3bcde --- /dev/null +++ b/src/Humanizer.Tests/DateTimeHumanizeSupportTimeExpressionsTests.cs @@ -0,0 +1,138 @@ +using System; +using System.Globalization; +using Humanizer.Localisation; +using Xunit; + +namespace Humanizer.Tests +{ + [UseCulture("en-US")] + public class DateTimeHumanizeSupportTimeExpressionsTests + { + + private static readonly TimeExpressionProviderToTest TimeExpressionProvider = new TimeExpressionProviderToTest(); + + [Theory] + [InlineData(1, "for one second")] + [InlineData(10, "for 10 seconds")] + [InlineData(59, "for 59 seconds")] + [InlineData(60, "for a minute")] + public void SecondsFor(int seconds, string expected) + { + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Past, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "in one second")] + [InlineData(10, "in 10 seconds")] + [InlineData(59, "in 59 seconds")] + [InlineData(60, "in a minute")] + public void SecondsIn(int seconds, string expected) + { + DateHumanize.Verify(expected, seconds, TimeUnit.Second, Tense.Future, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "for a minute")] + [InlineData(10, "for 10 minutes")] + [InlineData(44, "for 44 minutes")] + [InlineData(45, "for 45 minutes")] + [InlineData(59, "for 59 minutes")] + [InlineData(60, "for an hour")] + [InlineData(119, "for an hour")] + [InlineData(120, "for 2 hours")] + public void MinutesFor(int minutes, string expected) + { + DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Past, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "in a minute")] + [InlineData(10, "in 10 minutes")] + [InlineData(44, "in 44 minutes")] + [InlineData(45, "in 45 minutes")] + [InlineData(59, "in 59 minutes")] + [InlineData(60, "in an hour")] + [InlineData(119, "in an hour")] + [InlineData(120, "in 2 hours")] + public void MinutesIn(int minutes, string expected) + { + DateHumanize.Verify(expected, minutes, TimeUnit.Minute, Tense.Future, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "for an hour")] + [InlineData(10, "for 10 hours")] + [InlineData(23, "for 23 hours")] + [InlineData(24, "for one day")] + public void HoursFor(int hours, string expected) + { + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Past, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "in an hour")] + [InlineData(10, "in 10 hours")] + [InlineData(23, "in 23 hours")] + [InlineData(24, "tomorrow")] + public void HoursIn(int hours, string expected) + { + DateHumanize.Verify(expected, hours, TimeUnit.Hour, Tense.Future, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "for one day")] + [InlineData(10, "for 10 days")] + [InlineData(27, "for 27 days")] + [InlineData(32, "for one month")] + public void DaysFor(int days, string expected) + { + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Past, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "tomorrow")] + [InlineData(10, "in 10 days")] + [InlineData(27, "in 27 days")] + [InlineData(32, "in one month")] + public void DaysIn(int days, string expected) + { + DateHumanize.Verify(expected, days, TimeUnit.Day, Tense.Future, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "for one month")] + [InlineData(10, "for 10 months")] + [InlineData(11, "for 11 months")] + [InlineData(12, "for one year")] + public void MonthsFor(int months, string expected) + { + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Past, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "in one month")] + [InlineData(10, "in 10 months")] + [InlineData(11, "in 11 months")] + [InlineData(12, "in one year")] + public void MonthsIn(int months, string expected) + { + DateHumanize.Verify(expected, months, TimeUnit.Month, Tense.Future, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "for one year")] + [InlineData(2, "for 2 years")] + public void YearsFor(int years, string expected) + { + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Past, dateTimeExpressionProvider: TimeExpressionProvider); + } + + [Theory] + [InlineData(1, "in one year")] + [InlineData(2, "in 2 years")] + public void YearsIn(int years, string expected) + { + DateHumanize.Verify(expected, years, TimeUnit.Year, Tense.Future, dateTimeExpressionProvider: TimeExpressionProvider); + } + } +} diff --git a/src/Humanizer.Tests/TimeExpressionProviderToTest.cs b/src/Humanizer.Tests/TimeExpressionProviderToTest.cs new file mode 100644 index 000000000..23d91b499 --- /dev/null +++ b/src/Humanizer.Tests/TimeExpressionProviderToTest.cs @@ -0,0 +1,17 @@ +using Humanizer.Localisation; + +namespace Humanizer.Tests +{ + public class TimeExpressionProviderToTest : DateTimeExpressionProvider + { + public override TimeExpressionPast GetPastTimeExpression() + { + return TimeExpressionPast.For; + } + + public override TimeExpressionFuture GetFutureTimeExpression() + { + return TimeExpressionFuture.In; + } + } +} diff --git a/src/Humanizer/DateHumanizeExtensions.cs b/src/Humanizer/DateHumanizeExtensions.cs index 7047b2f69..62f8eeec3 100644 --- a/src/Humanizer/DateHumanizeExtensions.cs +++ b/src/Humanizer/DateHumanizeExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using Humanizer.Configuration; +using Humanizer.Localisation; namespace Humanizer { @@ -17,7 +18,7 @@ public static class DateHumanizeExtensions /// 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, CultureInfo culture = null) + public static string Humanize(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null, CultureInfo culture = null, DateTimeExpressionProvider dateTimeExpressionProvider = null) { var comparisonBase = dateToCompareAgainst ?? DateTime.UtcNow; @@ -26,7 +27,7 @@ public static string Humanize(this DateTime input, bool utcDate = true, DateTime comparisonBase = comparisonBase.ToLocalTime(); } - return Configurator.DateTimeHumanizeStrategy.Humanize(input, comparisonBase, culture); + return Configurator.DateTimeHumanizeStrategy.Humanize(input, comparisonBase, culture, dateTimeExpressionProvider); } /// @@ -82,4 +83,4 @@ public static string Humanize(this DateTimeOffset? input, DateTimeOffset? dateTo } } } -} \ No newline at end of file +} diff --git a/src/Humanizer/DateTimeHumanizeStrategy/DateTimeHumanizeAlgorithms.cs b/src/Humanizer/DateTimeHumanizeStrategy/DateTimeHumanizeAlgorithms.cs index 1b25eea6e..d2076f1a8 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/DateTimeHumanizeAlgorithms.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/DateTimeHumanizeAlgorithms.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using Humanizer.Configuration; using Humanizer.Localisation; @@ -13,7 +13,7 @@ internal static class DateTimeHumanizeAlgorithms /// /// Returns localized & humanized distance of time between two dates; given a specific precision. /// - public static string PrecisionHumanize(DateTime input, DateTime comparisonBase, double precision, CultureInfo culture) + public static string PrecisionHumanize(DateTime input, DateTime comparisonBase, double precision, CultureInfo culture, DateTimeExpressionProvider dateTimeExpressionProvider = null) { var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); var tense = input > comparisonBase ? Tense.Future : Tense.Past; @@ -72,42 +72,42 @@ public static string PrecisionHumanize(DateTime input, DateTime comparisonBase, var formatter = Configurator.GetFormatter(culture); if (years > 0) { - return formatter.DateHumanize(TimeUnit.Year, tense, years); + return formatter.DateHumanize(TimeUnit.Year, tense, years, dateTimeExpressionProvider); } if (months > 0) { - return formatter.DateHumanize(TimeUnit.Month, tense, months); + return formatter.DateHumanize(TimeUnit.Month, tense, months, dateTimeExpressionProvider); } if (days > 0) { - return formatter.DateHumanize(TimeUnit.Day, tense, days); + return formatter.DateHumanize(TimeUnit.Day, tense, days, dateTimeExpressionProvider); } if (hours > 0) { - return formatter.DateHumanize(TimeUnit.Hour, tense, hours); + return formatter.DateHumanize(TimeUnit.Hour, tense, hours, dateTimeExpressionProvider); } if (minutes > 0) { - return formatter.DateHumanize(TimeUnit.Minute, tense, minutes); + return formatter.DateHumanize(TimeUnit.Minute, tense, minutes, dateTimeExpressionProvider); } if (seconds > 0) { - return formatter.DateHumanize(TimeUnit.Second, tense, seconds); + return formatter.DateHumanize(TimeUnit.Second, tense, seconds, dateTimeExpressionProvider); } - return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, dateTimeExpressionProvider); } // http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time /// /// Calculates the distance of time in words between two provided dates /// - public static string DefaultHumanize(DateTime input, DateTime comparisonBase, CultureInfo culture) + public static string DefaultHumanize(DateTime input, DateTime comparisonBase, CultureInfo culture, DateTimeExpressionProvider dateTimeExpressionProvider = null) { var tense = input > comparisonBase ? Tense.Future : Tense.Past; var ts = new TimeSpan(Math.Abs(comparisonBase.Ticks - input.Ticks)); @@ -116,59 +116,59 @@ public static string DefaultHumanize(DateTime input, DateTime comparisonBase, Cu if (ts.TotalMilliseconds < 500) { - return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0); + return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0, dateTimeExpressionProvider); } if (ts.TotalSeconds < 60) { - return formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds); + return formatter.DateHumanize(TimeUnit.Second, tense, ts.Seconds, dateTimeExpressionProvider); } if (ts.TotalSeconds < 120) { - return formatter.DateHumanize(TimeUnit.Minute, tense, 1); + return formatter.DateHumanize(TimeUnit.Minute, tense, 1, dateTimeExpressionProvider); } if (ts.TotalMinutes < 60) { - return formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes); + return formatter.DateHumanize(TimeUnit.Minute, tense, ts.Minutes, dateTimeExpressionProvider); } if (ts.TotalMinutes < 90) { - return formatter.DateHumanize(TimeUnit.Hour, tense, 1); + return formatter.DateHumanize(TimeUnit.Hour, tense, 1, dateTimeExpressionProvider); } if (ts.TotalHours < 24) { - return formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours); + return formatter.DateHumanize(TimeUnit.Hour, tense, ts.Hours, dateTimeExpressionProvider); } if (ts.TotalHours < 48) { var days = Math.Abs((input.Date - comparisonBase.Date).Days); - return formatter.DateHumanize(TimeUnit.Day, tense, days); + return formatter.DateHumanize(TimeUnit.Day, tense, days, dateTimeExpressionProvider); } if (ts.TotalDays < 28) { - return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, dateTimeExpressionProvider); } 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); + return formatter.DateHumanize(TimeUnit.Month, tense, 1, dateTimeExpressionProvider); } - return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days); + return formatter.DateHumanize(TimeUnit.Day, tense, ts.Days, dateTimeExpressionProvider); } if (ts.TotalDays < 345) { var months = Convert.ToInt32(Math.Floor(ts.TotalDays / 29.5)); - return formatter.DateHumanize(TimeUnit.Month, tense, months); + return formatter.DateHumanize(TimeUnit.Month, tense, months, dateTimeExpressionProvider); } var years = Convert.ToInt32(Math.Floor(ts.TotalDays / 365)); @@ -177,7 +177,7 @@ public static string DefaultHumanize(DateTime input, DateTime comparisonBase, Cu years = 1; } - return formatter.DateHumanize(TimeUnit.Year, tense, years); + return formatter.DateHumanize(TimeUnit.Year, tense, years, dateTimeExpressionProvider); } } -} \ No newline at end of file +} diff --git a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs index e8c97fdb0..7ee4fd3eb 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/DefaultDateTimeHumanizeStrategy.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Globalization; +using Humanizer.Localisation; namespace Humanizer.DateTimeHumanizeStrategy { @@ -11,9 +12,9 @@ public class DefaultDateTimeHumanizeStrategy : IDateTimeHumanizeStrategy /// /// Calculates the distance of time in words between two provided dates /// - public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture) + public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture, DateTimeExpressionProvider dateTimeTextProvider) { - return DateTimeHumanizeAlgorithms.DefaultHumanize(input, comparisonBase, culture); + return DateTimeHumanizeAlgorithms.DefaultHumanize(input, comparisonBase, culture, dateTimeTextProvider); } } -} \ No newline at end of file +} diff --git a/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs index 7d663a794..5974d8dba 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/IDateTimeHumanizeStrategy.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using Humanizer.Localisation; namespace Humanizer.DateTimeHumanizeStrategy { @@ -11,6 +12,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, CultureInfo culture); + string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture, DateTimeExpressionProvider dateTimeTextProvider); } -} \ No newline at end of file +} diff --git a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs index 37bef4b9e..bd5283c79 100644 --- a/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs +++ b/src/Humanizer/DateTimeHumanizeStrategy/PrecisionDateTimeHumanizeStrategy.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Globalization; +using Humanizer.Localisation; namespace Humanizer.DateTimeHumanizeStrategy { @@ -22,9 +23,9 @@ public PrecisionDateTimeHumanizeStrategy(double precision = .75) /// /// Returns localized & humanized distance of time between two dates; given a specific precision. /// - public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture) + public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo culture, DateTimeExpressionProvider dateTimeExpressionProvider) { - return DateTimeHumanizeAlgorithms.PrecisionHumanize(input, comparisonBase, _precision, culture); + return DateTimeHumanizeAlgorithms.PrecisionHumanize(input, comparisonBase, _precision, culture, dateTimeExpressionProvider); } } -} \ No newline at end of file +} diff --git a/src/Humanizer/Localisation/DateTimeExpressionProvider.cs b/src/Humanizer/Localisation/DateTimeExpressionProvider.cs new file mode 100644 index 000000000..705202d05 --- /dev/null +++ b/src/Humanizer/Localisation/DateTimeExpressionProvider.cs @@ -0,0 +1,24 @@ +using System; + +namespace Humanizer.Localisation +{ + public class DateTimeExpressionProvider + { + private static readonly DateTimeExpressionProvider _dateTimeExpressionProvider = new DateTimeExpressionProvider(); + + public static DateTimeExpressionProvider Default + { + get { return _dateTimeExpressionProvider; } + } + + public virtual TimeExpressionFuture GetFutureTimeExpression() + { + return TimeExpressionFuture.FromNow; + } + + public virtual TimeExpressionPast GetPastTimeExpression() + { + return TimeExpressionPast.Ago; + } + } +} diff --git a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs index 341437042..660c8391a 100644 --- a/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs @@ -44,9 +44,9 @@ public virtual string DateHumanize_Never() /// /// /// - public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit) + public virtual string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, DateTimeExpressionProvider dateTimeTextProvider = null) { - return GetResourceForDate(timeUnit, timeUnitTense, unit); + return GetResourceForDate(timeUnit, timeUnitTense, unit, dateTimeTextProvider); } /// @@ -71,9 +71,9 @@ public virtual string TimeSpanHumanize(TimeUnit timeUnit, int unit, bool toWords return GetResourceForTimeSpan(timeUnit, unit, toWords); } - private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count) + private string GetResourceForDate(TimeUnit unit, Tense timeUnitTense, int count, DateTimeExpressionProvider dateTimeExpressionProvider = null) { - var resourceKey = ResourceKeys.DateHumanize.GetResourceKey(unit, timeUnitTense: timeUnitTense, count: count); + var resourceKey = ResourceKeys.DateHumanize.GetResourceKey(unit, timeUnitTense: timeUnitTense, count: count, dateTimeExpressionProvider: dateTimeExpressionProvider); return count == 1 ? Format(resourceKey) : Format(resourceKey, count); } diff --git a/src/Humanizer/Localisation/Formatters/IFormatter.cs b/src/Humanizer/Localisation/Formatters/IFormatter.cs index 398fbd4fd..e3f641323 100644 --- a/src/Humanizer/Localisation/Formatters/IFormatter.cs +++ b/src/Humanizer/Localisation/Formatters/IFormatter.cs @@ -26,7 +26,7 @@ public interface IFormatter /// /// /// - string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit); + string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit, DateTimeExpressionProvider dateTimeExpressionProvider = null); /// /// 0 seconds diff --git a/src/Humanizer/Localisation/ResourceKeys.DateHumanize.cs b/src/Humanizer/Localisation/ResourceKeys.DateHumanize.cs index 89a3a50ff..a03c1c251 100644 --- a/src/Humanizer/Localisation/ResourceKeys.DateHumanize.cs +++ b/src/Humanizer/Localisation/ResourceKeys.DateHumanize.cs @@ -23,9 +23,6 @@ public static class DateHumanize /// private const string DateTimeFormat = "DateHumanize_{0}{1}{2}"; - private const string Ago = "Ago"; - private const string FromNow = "FromNow"; - /// /// Generates Resource Keys accordning to convention. /// @@ -33,7 +30,7 @@ public static class DateHumanize /// Is time unit in future or past /// Number of units, default is One. /// Resource key, like DateHumanize_SingleMinuteAgo - public static string GetResourceKey(TimeUnit timeUnit, Tense timeUnitTense, int count = 1) + public static string GetResourceKey(TimeUnit timeUnit, Tense timeUnitTense, int count = 1, DateTimeExpressionProvider dateTimeExpressionProvider = null) { ValidateRange(count); @@ -43,7 +40,10 @@ public static string GetResourceKey(TimeUnit timeUnit, Tense timeUnitTense, int } var singularity = count == 1 ? Single : Multiple; - var tense = timeUnitTense == Tense.Future ? FromNow : Ago; + + dateTimeExpressionProvider ??= DateTimeExpressionProvider.Default; + + var tense = timeUnitTense == Tense.Future ? dateTimeExpressionProvider.GetFutureTimeExpression().ToString() : dateTimeExpressionProvider.GetPastTimeExpression().ToString(); var unit = timeUnit.ToString().ToQuantity(count, ShowQuantityAs.None); return DateTimeFormat.FormatWith(singularity, unit, tense); } diff --git a/src/Humanizer/Localisation/TimeExpressionFuture.cs b/src/Humanizer/Localisation/TimeExpressionFuture.cs new file mode 100644 index 000000000..4495520d9 --- /dev/null +++ b/src/Humanizer/Localisation/TimeExpressionFuture.cs @@ -0,0 +1,12 @@ +namespace Humanizer.Localisation +{ + /// + /// Enumerates the possible time expressions + /// + public enum TimeExpressionFuture + { + FromNow, + For, + In + } +} diff --git a/src/Humanizer/Localisation/TimeExpressionPast.cs b/src/Humanizer/Localisation/TimeExpressionPast.cs new file mode 100644 index 000000000..eab23f47a --- /dev/null +++ b/src/Humanizer/Localisation/TimeExpressionPast.cs @@ -0,0 +1,11 @@ +namespace Humanizer.Localisation +{ + /// + /// Enumerates the possible time expressions + /// + public enum TimeExpressionPast + { + Ago, + For + } +} diff --git a/src/Humanizer/Properties/Resources.resx b/src/Humanizer/Properties/Resources.resx index 678f0d5cf..d11b8a0a8 100644 --- a/src/Humanizer/Properties/Resources.resx +++ b/src/Humanizer/Properties/Resources.resx @@ -675,4 +675,283 @@ NNW + + for {0} days + + + for {0} days + + + for {0} days + + + for {0} days + + + for {0} day + + + for {0} days + + + for {0} hours + + + for {0} hours + + + for {0} hours + + + for {0} hours + + + for {0} hours + + + for {0} hour + + + for {0} hours + + + for {0} minutes + + + for {0} minutes + + + for {0} minutes + + + for {0} minutes + + + for {0} minutes + + + for {0} minute + + + for {0} minutes + + + for {0} months + + + for {0} months + + + for {0} months + + + for {0} months + + + for {0} month + + + for {0} months + + + for {0} seconds + + + for {0} seconds + + + for {0} seconds + + + for {0} seconds + + + for {0} seconds + + + for {0} second + + + for {0} seconds + + + for {0} years + + + for {0} years + + + for {0} years + + + for {0} years + + + for {0} years + + + for {0} year + + + for {0} years + + + + + + in {0} days + + + in {0} days + + + in {0} days + + + in {0} day + + + in {0} days + + + in {0} days + + + in {0} hours + + + in {0} hours + + + in {0} hours + + + in {0} hours + + + in {0} hours + + + in {0} hour + + + in {0} hours + + + in {0} minutes + + + in {0} minutes + + + in {0} minutes + + + in {0} minutes + + + in {0} minutes + + + in {0} minute + + + in {0} minutes + + + in {0} months + + + in {0} months + + + in {0} months + + + in {0} months + + + in {0} month + + + in {0} months + + + in {0} seconds + + + in {0} seconds + + + in {0} seconds + + + in {0} seconds + + + in {0} seconds + + + in {0} second + + + in {0} seconds + + + in {0} years + + + in {0} years + + + in {0} years + + + in {0} years + + + in {0} years + + + in {0} year + + + in {0} years + + + for one day + + + tomorrow + + + for an hour + + + in an hour + + + for a minute + + + in a minute + + + for one month + + + in one month + + + for one second + + + in one second + + + for one year + + + in one year + \ No newline at end of file