is there a smarter way to generate "time since" with a DateTime objects

As an alternative, I have a solution that does that beyond days with weeks, months and years. The approach is a bit different It advances from the past to the future, first trying the big steps and if it overshoots switching to the next smaller one.

PeriodOfTimeOutput.cs


A very late answer, but I felt the need for this, and searching for common JS terms such as "C# momentjs datetime" or "C# timeago" showed results which were not at all helpful - I don't want to maintain extra code with hardcoded magic numbers and which won't be localization-friendly. So, finally, in one of the comments in another SO answer, I found the library:

Humanizer for .NET - https://github.com/Humanizr/Humanizer#humanize-datetime

Usage:

DateTime.UtcNow.AddHours(-2).Humanize() => "2 hours ago"

And it's localizable too!


Use the TotalHours property or other Total[TimeUnit] properties in the timespan object.

For a timespan of 1:10 (hh:mm), it equates to 1 Hours and 10 Minutes or 1.167 TotalHours and 70 TotalMinutes.


As for cleaning it up, stick to using if/else branches as you had earlier. switch/case will not help you with these conditions, only for specific values. Something like this:

DateTime when = GetDateTimeinPast();
TimeSpan ts = DateTime.Now.Subtract(when);
if (ts.TotalHours < 1)
    b.AppendFormat("{0} minutes ago", (int)ts.TotalMinutes);
else if (ts.TotalDays < 1)
    b.AppendFormat("{0} hours ago", (int)ts.TotalHours);
//etc...

C# 8 and up, you could use switch expressions and property patterns to condense it further to a single expression.

(DateTime.Now - when) switch
{
    { TotalHours: < 1 } ts => $"{ts.Minutes} minutes ago",
    { TotalDays: < 1 } ts => $"{ts.Hours} hours ago",
    { TotalDays: < 2 } => $"yesterday",
    { TotalDays: < 5 } => $"on {when.DayOfWeek}",
    var ts => $"{ts.Days} days ago",
};