Timeago for C#
Sometimes you don’t want to make your users think. There’s the odd situation where you want to represent time in natural language: “about 4 hours ago” instead of just printing out a full timestamp. If you’re building a website, then the jQuery plugin Timeago is a pretty sweet way to do it (as long as you can stand webpages that auto update text).
Sucks for me, I’m working with WPF! (Not really sucks at all). So I needed a C# implementation of the same thing. Surely someone’s done this, right? Well my Google-fu failed me, and even when I Googled on bing I came up with nothing, so I built it myself. And I’m posting it here for you (and for me, later). If you’ve found a good one, please let me know.
First, the test cases, so you can see if the format I want is the format you want:
[TestClass]
public class FriendlyTimeDescriptionTest
{
private static string Run(TimeSpan span)
{
return FriendlyTimeDescription.Describe(span);
}
[TestMethod]
public void TestNow()
{
Assert.AreEqual("now", Run(new TimeSpan(0, 0, 0, 0)));
}
[TestMethod]
public void TestSeconds()
{
Assert.AreEqual("1 second ago", Run(new TimeSpan(0, 0, 0, 1)));
Assert.AreEqual("2 seconds ago", Run(new TimeSpan(0, 0, 0, 2)));
Assert.AreEqual("59 seconds ago", Run(new TimeSpan(0, 0, 0, 59)));
}
[TestMethod]
public void TestMinutesAndSeconds()
{
Assert.AreEqual("about 1 minute ago", Run(new TimeSpan(0, 0, 1, 1)));
Assert.AreEqual("about 3 minutes ago", Run(new TimeSpan(0, 0, 3, 1)));
}
[TestMethod]
public void TestMinutesAndSecondsRounding()
{
Assert.AreEqual("about 4 minutes ago", Run(new TimeSpan(0, 0, 3, 31)));
}
[TestMethod]
public void TestDaysHours()
{
Assert.AreEqual("about 3 hours ago", Run(new TimeSpan(0, 3, 3, 1)));
Assert.AreEqual("about 2 days ago", Run(new TimeSpan(2, 0, 1, 1)));
}
}
I’ve conveniently wrapped all this up into an IValueConverter implementation, but if you’re not using WPF you can rip out the necessary methods. Please excuse the newline-enthused formatting – this blog theme has limited column width!
[ValueConversion(typeof(DateTime), typeof(string))]
public class FriendlyTimeDescription : IValueConverter
{
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
var time = System.Convert.ToDateTime(value);
return Describe(DateTime.Now - time);
}
static readonly string[] NAMES = {
"day",
"hour",
"minute",
"second"
};
public static string Describe(TimeSpan t)
{
int[] ints = {
t.Days,
t.Hours,
t.Minutes,
t.Seconds
};
double[] doubles = {
t.TotalDays,
t.TotalHours,
t.TotalMinutes,
t.TotalSeconds
};
var firstNonZero = ints
.Select((value, index) => new { value, index })
.FirstOrDefault(x => x.value != 0);
if (firstNonZero == null)
{
return "now";
}
int i = firstNonZero.index;
string prefix = (i >= 3) ? "" : "about ";
int quantity = (int)Math.Round(doubles[i]);
return prefix + Tense(quantity, NAMES[i]) + " ago";
}
public static string Tense(int quantity, string noun)
{
return quantity == 1
? "1 " + noun
: string.Format("{0} {1}s", quantity, noun);
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return Binding.DoNothing;
}
}
Enjoy!


