Last active
November 6, 2018 13:02
-
-
Save michaelvolz/eaaf4033e6167aa42182f964bbd52574 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class DateTimeExtensions | |
{ | |
private const int Today = 0; | |
private const int Yesterday = 1; | |
private const int Week = 7; | |
private const int Month = 31; | |
private const int OneMinute = 60; | |
private const int OneHour = 3600; | |
private const int OneDay = 86400; | |
public static string ToFriendlyDate(this DateTime? date) | |
{ | |
return date.HasValue ? date.Value.ToFriendlyDate() : string.Empty; | |
} | |
public static string ToFriendlyDate(this DateTime date) | |
{ | |
var elapsedTime = DateTime.UtcNow.Subtract(date); | |
var totalDaysElapsed = (int) elapsedTime.TotalDays; | |
var totalSecondsElapsed = (int) elapsedTime.TotalSeconds; | |
switch (totalDaysElapsed) | |
{ | |
case int _ when totalSecondsElapsed < 0 : return "n/a"; | |
case Today when totalSecondsElapsed < OneMinute: return "just now"; | |
case Today when totalSecondsElapsed < OneHour: | |
return string.Format(new PluralFormatProvider(), "{0:minute;minutes} ago", | |
totalSecondsElapsed.FloorBy(OneMinute)); | |
case Today when totalSecondsElapsed < OneDay: | |
return string.Format(new PluralFormatProvider(), "{0:hour;hours} ago", | |
totalSecondsElapsed.FloorBy(OneHour)); | |
case Yesterday: return "yesterday"; | |
case int days when days < Week: return $"{totalDaysElapsed} days ago"; | |
case int days when days < Month: | |
return string.Format(new PluralFormatProvider(), "{0:week;weeks} ago", | |
totalDaysElapsed.CeilingBy(Week)); | |
default: | |
return date.ToString(CultureInfo.InvariantCulture); | |
} | |
} | |
private static double CeilingBy(this int dividend, int divisor) => Math.Ceiling((double) dividend / divisor); | |
private static double FloorBy(this int dividend, int divisor) => Math.Floor((double) dividend / divisor); | |
private class PluralFormatProvider : IFormatProvider, ICustomFormatter | |
{ | |
public string Format(string format, object arg, IFormatProvider formatProvider) => | |
$"{Convert.ToInt32(arg)} {format.Split(';')[Convert.ToInt32(arg) == 1 ? 0 : 1]}"; | |
public object GetFormat(Type formatType) => this; | |
} | |
} | |
--- | |
using System; | |
using FluentAssertions; | |
using Xunit; | |
public class DateTimeExtensionsTests | |
{ | |
public static object[][] TestData => new[] | |
{ | |
new object[] {DateTime.UtcNow, "just now"}, | |
new object[] {DateTime.UtcNow.AddMinutes(-1), "1 minute ago"}, | |
new object[] {DateTime.UtcNow.AddMinutes(-2), "2 minutes ago"}, | |
new object[] {DateTime.UtcNow.AddMinutes(-60), "1 hour ago"}, | |
new object[] {DateTime.UtcNow.AddMinutes(-120), "2 hours ago"}, | |
new object[] {DateTime.UtcNow.AddDays(-1), "yesterday"}, | |
new object[] {DateTime.UtcNow.AddDays(-2), "2 days ago"}, | |
new object[] {DateTime.UtcNow.AddDays(-7), "1 week ago"}, | |
new object[] {DateTime.UtcNow.AddDays(-30), "5 weeks ago"} | |
}; | |
[Theory] | |
[MemberData(nameof(TestData))] | |
public void ToFriendlyDate_ValidDates(DateTime date, string expected) | |
{ | |
var result = date.ToFriendlyDate(); | |
result.Should().Be(expected); | |
} | |
[Fact] | |
public void ToFriendlyDate_FutureDate_Invalid() | |
{ | |
var result = DateTime.UtcNow.AddMinutes(5).ToFriendlyDate(); | |
result.Should().Be("n/a"); | |
} | |
[Fact] | |
public void ToFriendlyDate_OldDate() | |
{ | |
var date = DateTime.UtcNow.AddMonths(-3); | |
var result = date.ToFriendlyDate(); | |
result.Should().Be(date.ToString(CultureInfo.InvariantCulture)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment