Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for humanizing DateTimeOffset #399

Merged
merged 4 commits into from
Mar 28, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ By default both methods throw a `NoMatchFoundException` when they cannot match t
In the non-generic method you can also ask the method to return null by setting the second optional parameter to `NoMatch.ReturnsNull`.

###<a id="humanize-datetime">Humanize DateTime</a>
You can `Humanize` an instance of `DateTime` and get back a string telling how far back or forward in time that is:
You can `Humanize` an instance of `DateTime` or `DateTimeOffset` and get back a string telling how far back or forward in time that is:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for adding to the readme.


```C#
DateTime.UtcNow.AddHours(-30).Humanize() => "yesterday"
Expand All @@ -273,12 +273,13 @@ DateTime.UtcNow.AddHours(30).Humanize() => "tomorrow"
DateTime.UtcNow.AddHours(2).Humanize() => "2 hours from now"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an example for DateTimeOffset here.

```

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.
Humanizer supports both local and UTC dates as well as dates with offset (`DateTimeOffset`). 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, CultureInfo culture = null)
public static string Humanize(this DateTimeOffset input, DateTimeOffset? dateToCompareAgainst = null, CultureInfo culture = null)
```

Many localizations are available for this method. Here is a few examples:
Expand All @@ -302,7 +303,10 @@ DateTime.UtcNow.AddMinutes(-40).Humanize() => "40 минут назад"
There are two strategies for `DateTime.Humanize`: the default one as seen above and a precision based one.
To use the precision based strategy you need to configure it:

`Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision = .75)`.
```C#
Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision = .75);
Configurator.DateTimeOffsetHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(precision = .75); // configure when humanizing DateTimeOffset
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I find it weird that you need to configure DateTimeOffset strategy to point to DateTime strategy. We need to create an explicit class for DateTimeOffset.

```

The default precision is set to .75 but you can pass your desired precision too. With precision set to 0.75:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public class Configurator
{
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.CollectionFormatters.ICollectionFormatter> CollectionFormatters { get; }
public Humanizer.DateTimeHumanizeStrategy.IDateTimeHumanizeStrategy DateTimeHumanizeStrategy { get; set; }
public Humanizer.DateTimeHumanizeStrategy.IDateTimeOffsetHumanizeStrategy DateTimeOffsetHumanizeStrategy { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI the Approver text is a very clean approach to make sure we're not introducing breaking changes. That's all. Thanks for updating it.

public System.Func<System.Reflection.PropertyInfo, bool> EnumDescriptionPropertyLocator { get; set; }
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.Formatters.IFormatter> Formatters { get; }
public Humanizer.Configuration.LocaliserRegistry<Humanizer.Localisation.NumberToWords.INumberToWordsConverter> NumberToWordsConverters { get; }
Expand All @@ -144,23 +145,31 @@ public class LocaliserRegistry`1
public class DateHumanizeExtensions
{
public string Humanize(System.DateTime input, bool utcDate, System.Nullable<System.DateTime> dateToCompareAgainst, System.Globalization.CultureInfo culture) { }
public string Humanize(System.DateTimeOffset input, System.Nullable<System.DateTimeOffset> dateToCompareAgainst, System.Globalization.CultureInfo culture) { }
}

public class DefaultDateTimeHumanizeStrategy
{
public DefaultDateTimeHumanizeStrategy() { }
public string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture) { }
public string Humanize(System.DateTimeOffset input, System.DateTimeOffset comparisonBase, System.Globalization.CultureInfo culture) { }
}

public interface IDateTimeHumanizeStrategy
{
string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture);
}

public interface IDateTimeOffsetHumanizeStrategy
{
string Humanize(System.DateTimeOffset input, System.DateTimeOffset comparisonBase, System.Globalization.CultureInfo culture);
}

public class PrecisionDateTimeHumanizeStrategy
{
public PrecisionDateTimeHumanizeStrategy(double precision) { }
public string Humanize(System.DateTime input, System.DateTime comparisonBase, System.Globalization.CultureInfo culture) { }
public string Humanize(System.DateTimeOffset input, System.DateTimeOffset comparisonBase, System.Globalization.CultureInfo culture) { }
}

public class EnumDehumanizeExtensions
Expand Down
73 changes: 73 additions & 0 deletions src/Humanizer.Tests/DateTimeOffsetHumanizeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Humanizer.Configuration;
using Humanizer.DateTimeHumanizeStrategy;
using Xunit;

namespace Humanizer.Tests
{
public class DateTimeOffsetHumanizeTests : AmbientCulture
{
public DateTimeOffsetHumanizeTests() : base("en-US")
{
}

[Fact]
public void DefaultStrategy_SameOffset()
{
Configurator.DateTimeOffsetHumanizeStrategy = new DefaultDateTimeHumanizeStrategy();

var inputTime = new DateTimeOffset(2015, 07, 05, 04, 0, 0, TimeSpan.Zero);
var baseTime = new DateTimeOffset(2015, 07, 05, 03, 0, 0, TimeSpan.Zero);

const string expectedResult = "an hour from now";
var actualResult = inputTime.Humanize(baseTime);

Assert.Equal(expectedResult, actualResult);
}

[Fact]
public void DefaultStrategy_DifferentOffsets()
{
Configurator.DateTimeOffsetHumanizeStrategy = new DefaultDateTimeHumanizeStrategy();

var inputTime = new DateTimeOffset(2015, 07, 05, 03, 0, 0, new TimeSpan(2, 0, 0));
var baseTime = new DateTimeOffset(2015, 07, 05, 02, 30, 0, new TimeSpan(1, 0, 0));

const string expectedResult = "30 minutes ago";
var actualResult = inputTime.Humanize(baseTime);

Assert.Equal(expectedResult, actualResult);
}

[Fact]
public void PrecisionStrategy_SameOffset()
{
Configurator.DateTimeOffsetHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(0.75);

var inputTime = new DateTimeOffset(2015, 07, 05, 04, 0, 0, TimeSpan.Zero);
var baseTime = new DateTimeOffset(2015, 07, 04, 05, 0, 0, TimeSpan.Zero);

const string expectedResult = "tomorrow";
var actualResult = inputTime.Humanize(baseTime);

Assert.Equal(expectedResult, actualResult);
}

[Fact]
public void PrecisionStrategy_DifferentOffsets()
{
Configurator.DateTimeOffsetHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(0.75);

var inputTime = new DateTimeOffset(2015, 07, 05, 03, 45, 0, new TimeSpan(2, 0, 0));
var baseTime = new DateTimeOffset(2015, 07, 05, 02, 30, 0, new TimeSpan(-5, 0, 0));

const string expectedResult = "6 hours ago";
var actualResult = inputTime.Humanize(baseTime);

Assert.Equal(expectedResult, actualResult);
}
}
}
1 change: 1 addition & 0 deletions src/Humanizer.Tests/Humanizer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Compile Include="CasingTests.cs" />
<Compile Include="DateHumanize.cs" />
<Compile Include="CollectionHumanizeTests.cs" />
<Compile Include="DateTimeOffsetHumanizeTests.cs" />
<Compile Include="EnumHumanizeWithCustomDescriptionPropertyNamesTests.cs" />
<Compile Include="Localisation\bg\DateHumanizeTests.cs" />
<Compile Include="Localisation\bg\TimeSpanHumanizeTests.cs" />
Expand Down
10 changes: 10 additions & 0 deletions src/Humanizer/Configuration/Configurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ public static IDateTimeHumanizeStrategy DateTimeHumanizeStrategy
set { _dateTimeHumanizeStrategy = value; }
}

private static IDateTimeOffsetHumanizeStrategy _dateTimeOffsetHumanizeStrategy = new DefaultDateTimeHumanizeStrategy();
/// <summary>
/// The strategy to be used for DateTimeOffset.Humanize
/// </summary>
public static IDateTimeOffsetHumanizeStrategy DateTimeOffsetHumanizeStrategy
{
get { return _dateTimeOffsetHumanizeStrategy; }
set { _dateTimeOffsetHumanizeStrategy = value; }
}

private static readonly Func<PropertyInfo, bool> DefaultEnumDescriptionPropertyLocator = p => p.Name == "Description";
private static Func<PropertyInfo, bool> _enumDescriptionPropertyLocator = DefaultEnumDescriptionPropertyLocator;
/// <summary>
Expand Down
14 changes: 14 additions & 0 deletions src/Humanizer/DateHumanizeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,19 @@ public static string Humanize(this DateTime input, bool utcDate = true, DateTime

return Configurator.DateTimeHumanizeStrategy.Humanize(input, comparisonBase, culture);
}

/// <summary>
/// Turns the current or provided date into a human readable sentence
/// </summary>
/// <param name="input">The date to be humanized</param>
/// <param name="dateToCompareAgainst">Date to compare the input against. If null, current date is used as base</param>
/// <param name="culture">Culture to use. If null, current thread's UI culture is used.</param>
/// <returns>distance of time in words</returns>
public static string Humanize(this DateTimeOffset input, DateTimeOffset? dateToCompareAgainst = null, CultureInfo culture = null)
{
var comparisonBase = dateToCompareAgainst ?? DateTimeOffset.UtcNow;

return Configurator.DateTimeOffsetHumanizeStrategy.Humanize(input, comparisonBase, culture);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Humanizer.DateTimeHumanizeStrategy
/// <summary>
/// The default 'distance of time' -> words calculator.
/// </summary>
public class DefaultDateTimeHumanizeStrategy : IDateTimeHumanizeStrategy
public class DefaultDateTimeHumanizeStrategy : IDateTimeHumanizeStrategy, IDateTimeOffsetHumanizeStrategy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a good idea. I think we need to have separate strategy classes for DateTimeOffset. I appreciate that the implementation is more or less the same and perhaps you can just delegate the responsibility to the other class or even better extract a class for the algorithm and use it in both implementations.

{
// http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time
/// <summary>
Expand Down Expand Up @@ -70,5 +70,17 @@ public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo cult

return formatter.DateHumanize(TimeUnit.Year, tense, years);
}

/// <summary>
/// Calculates the distance of time in words between two provided dates
/// </summary>
/// <param name="input"></param>
/// <param name="comparisonBase"></param>
/// <param name="culture"></param>
/// <returns></returns>
public string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo culture)
{
return Humanize(input.UtcDateTime, comparisonBase.UtcDateTime, culture);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Globalization;

namespace Humanizer.DateTimeHumanizeStrategy
{
/// <summary>
/// Implement this interface to create a new strategy for DateTime.Humanize and hook it in the Configurator.DateTimeOffsetHumanizeStrategy
/// </summary>
public interface IDateTimeOffsetHumanizeStrategy
{
/// <summary>
/// Calculates the distance of time in words between two provided dates used for DateTimeOffset.Humanize
/// </summary>
string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo culture);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Humanizer.DateTimeHumanizeStrategy
/// <summary>
///
/// </summary>
public class PrecisionDateTimeHumanizeStrategy : IDateTimeHumanizeStrategy
public class PrecisionDateTimeHumanizeStrategy : IDateTimeHumanizeStrategy, IDateTimeOffsetHumanizeStrategy
{
private readonly double _precision;

Expand Down Expand Up @@ -70,5 +70,17 @@ public string Humanize(DateTime input, DateTime comparisonBase, CultureInfo cult
if (seconds > 0) return formatter.DateHumanize(TimeUnit.Second, tense, seconds);
return formatter.DateHumanize(TimeUnit.Millisecond, tense, 0);
}

/// <summary>
/// Calculates the distance of time in words between two provided dates
/// </summary>
/// <param name="input"></param>
/// <param name="comparisonBase"></param>
/// <param name="culture"></param>
/// <returns></returns>
public string Humanize(DateTimeOffset input, DateTimeOffset comparisonBase, CultureInfo culture)
{
return Humanize(input.UtcDateTime, comparisonBase.UtcDateTime, culture);
}
}
}
1 change: 1 addition & 0 deletions src/Humanizer/Humanizer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<Compile Include="Bytes\ByteRate.cs" />
<Compile Include="CollectionHumanizeExtensions.cs" />
<Compile Include="Configuration\CollectionFormatterRegistry.cs" />
<Compile Include="DateTimeHumanizeStrategy\IDateTimeOffsetHumanizeStrategy.cs" />
<Compile Include="Localisation\CollectionFormatters\DefaultCollectionFormatter.cs" />
<Compile Include="Localisation\CollectionFormatters\RegularStyleCollectionFormatter.cs" />
<Compile Include="Localisation\CollectionFormatters\ICollectionFormatter.cs" />
Expand Down