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",
};