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

5

11

i have this code to take a time in the past and generate a readable string to represent how long ago it was.

  1. I would have thought Timespan.Hours would give you hours even if its multiple daye in the past but it looks like it breaks it down into its seperate components (days, months, etc). How would i get total hours ago (even if its more than 1 day?

  2. Is there any cleaner way to write this type of code below as it seems pretty spagetti-ish.

Here is the code

        DateTime when = GetDateTimeinPast();
        TimeSpan ts = DateTime.Now.Subtract(when);

        switch (ts.Days)
        {
            case 0:
               if (ts.Hours < 1)
                    b.Append( ts.Minutes + " minutes ago");
               else
                   b.Append( ts.Hours + " hours ago");
                break;
            case 1:
                b.Append( " yesterday");
                break;
            case 2:
            case 3:                
            case 4:

                b.Append( "on " + when.DayOfWeek.ToString());
                break;
            default:
                b.Append(ts.Days + " days ago");
                break;
        }
Oglethorpe answered 1/8, 2010 at 19:41 Comment(3)
@Lord.Quackstar - updated tags and questionOglethorpe
case 2 - 5: // This looks alarming.Apologetics
You probably want to look at the answers to this question: https://mcmap.net/q/45143/-calculate-relative-time-in-cDemoss
C
19

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",
};
Caoutchouc answered 1/8, 2010 at 19:48 Comment(1)
M - thanks. Any ideas on the second question or is this above is basically the solutionOglethorpe
D
3

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!

Delitescent answered 27/5, 2017 at 23:47 Comment(0)
S
1

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

Sciuroid answered 1/8, 2010 at 21:53 Comment(0)
T
1

I know this is an old post, but I came up with this solution using recursion after searching for this topic and reading Jeff Mercado's answer

private string PeriodOfTimeOutput(TimeSpan tspan, int level = 0)
{
    string how_long_ago = "ago";
    if (level >= 2) return how_long_ago;
    if (tspan.Days > 1)
        how_long_ago = string.Format("{0} Days ago", tspan.Days);
    else if (tspan.Days == 1)
        how_long_ago = string.Format("1 Day {0}", PeriodOfTimeOutput(new TimeSpan(tspan.Hours, tspan.Minutes, tspan.Seconds), level + 1));
    else if (tspan.Hours >= 1)
        how_long_ago = string.Format("{0} {1} {2}", tspan.Hours, (tspan.Hours > 1) ? "Hours" : "Hour", PeriodOfTimeOutput(new TimeSpan(0, tspan.Minutes, tspan.Seconds), level + 1));
    else if (tspan.Minutes >= 1)
        how_long_ago = string.Format("{0} {1} {2}", tspan.Minutes, (tspan.Minutes > 1) ? "Minutes" : "Minute", PeriodOfTimeOutput(new TimeSpan(0, 0, tspan.Seconds), level + 1));
    else if (tspan.Seconds >= 1)
        how_long_ago = string.Format("{0} {1} ago", tspan.Seconds, (tspan.Seconds > 1) ? "Seconds" : "Second");        
    return how_long_ago;
}

used as such

var tspan = DateTime.Now.Subtract(reqDate);
string how_long_ago = PeriodOfTimeOutput(tspan);
Terrorism answered 12/11, 2015 at 16:2 Comment(0)
S
1

If you want more flexibility and smarter-looking outcome, then there is an extension method for this in the Olive framework named ToTimeDifferenceString().

It has a parameter named precisionParts. For example:

myDate.ToTimeDifferenceString(1)

which returns "2 days ago"

or

myDate.ToTimeDifferenceString(2)

which returns "2 days and 4 hours ago"

Sector answered 22/3, 2018 at 14:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.