3
\$\begingroup\$

In lieu with several other Facebook-esque concepts I am trying to replicate, this working code will take the span of time from a given date to now, and try to convert it into the most convenient English-friendly word. For example, if I post a comment on an article or status, and I want to show when it was posted to a user who views it, it would indicate something like "5 minutes ago", versus a date itself.

Concerns: Does this cover all possible use cases? Is it done optimally in the least lines of code?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Schloss.Text
{
    public static class Date
    {
        public static string GetElapsedTime(DateTimeOffset datetime)
        {
            TimeSpan ts = DateTimeOffset.Now.Subtract(datetime);

            int years = ts.Days / 365;
            int months = ts.Days / 30;
            int weeks = ts.Days / 52;

            if (years == 1) // one year ago
                return "A year ago";

            if (years > 1) // greater than one year
            {
                if (ts.Days % 365 == 0) // even year
                    return (int)(ts.TotalDays / 365) + " years ago";
                else // not really entire years
                    return "About " + (int)(ts.TotalDays / 365) + " years ago";
            }

            if (months == 1) // one month
                return "About a month ago";

            if (months > 1) // more than one month
                return "About " + months + " months ago";

            if (weeks == 1) // a week ago
                return "About a week ago";

            if (weeks > 1) // more than a week ago, but less than a month ago
                return "About " + weeks + " weeks ago";

            if (ts.Days == 1) // one day ago
                return "Yesterday";

            if (ts.Days > 1) //  more than one day ago, but less than one week ago
                return ts.Days + " days ago";

            if (ts.Hours == 1) // An hour ago
                return "About an hour ago";

            if (ts.Hours > 1 && ts.Hours <= 24) // More than an hour ago, but less than a day ago
                return "About " + ts.Hours + " hours ago";

            if (ts.Minutes == 1)
                return "About a minute ago";

            if (ts.Minutes == 0)
                return ts.Seconds + " seconds ago";

            return ts.Minutes + " minutes ago";
        }
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ I have seen a dual system in use, where recent posts are "1 hour ago", "2 days ago" etc. but for earlier posts it switches to "20014-03-22" style date format you want. I am not quite sure where the switch over is done, but I can't remember seeing "1 week ago" so it is probably less than that. \$\endgroup\$
    – rossum
    Jun 10, 2015 at 18:38
  • 2
    \$\begingroup\$ Not all years are 365 days, not all months are 30 days. Jon Skeet's answer to "How to get difference between two dates in Year/Month/Week/Day?" will be helpful. \$\endgroup\$
    – BCdotWEB
    Jun 11, 2015 at 10:56

1 Answer 1

4
\$\begingroup\$

Does this cover all possible use cases?

Maybe, but it doesn't calculate timespan components (year, month, etc) exactly.

Is it done optimally in the least lines of code?

Probably no.
You could generalize check of different TimeSpan elements using the following approach:

private static string GetElapsedTime(DateTime datetime)
{
    TimeSpan ts = DateTime.Now.Subtract(datetime);

    // The trick: make variable contain date and time representing the desired timespan,
    // having +1 in each date component.
    DateTime date = DateTime.MinValue + ts;

    return ProcessPeriod(date.Year - 1, date.Month - 1, "year")
           ?? ProcessPeriod(date.Month - 1, date.Day - 1, "month")
           ?? ProcessPeriod(date.Day - 1, date.Hour, "day", "Yesterday")
           ?? ProcessPeriod(date.Hour, date.Minute, "hour")
           ?? ProcessPeriod(date.Minute, date.Second, "minute")
           ?? ProcessPeriod(date.Second, 0, "second")
           ?? "Right now";
}

private static string ProcessPeriod(int value, int subValue, string name, string singularName = null)
{
    if (value == 0)
    {
        return null;
    }
    if (value == 1)
    {
        if (!String.IsNullOrEmpty(singularName))
        {
            return singularName;
        }
        string articleSuffix = name[0] == 'h' ? "n" : String.Empty;
        return subValue == 0
            ? String.Format("A{0} {1} ago", articleSuffix, name)
            : String.Format("About a{0} {1} ago", articleSuffix, name);
    }
    return subValue == 0
        ? String.Format("{0} {1}s ago", value, name)
        : String.Format("About {0} {1}s ago", value, name);
}
\$\endgroup\$
3
  • \$\begingroup\$ Is the removal of the "yesterday" case personal taste? \$\endgroup\$ Jun 10, 2015 at 18:19
  • \$\begingroup\$ @TylerHarden No, I just not came up with how to generalize it, sorry. \$\endgroup\$
    – Dmitry
    Jun 10, 2015 at 18:24
  • \$\begingroup\$ @TylerHarden I've updated the answer. Now it should work not worse than yours. Please revise. \$\endgroup\$
    – Dmitry
    Jun 10, 2015 at 21:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.