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\$
  • \$\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 '15 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 '15 at 10:56
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\$
  • \$\begingroup\$ Is the removal of the "yesterday" case personal taste? \$\endgroup\$ – Tyler James Harden Jun 10 '15 at 18:19
  • \$\begingroup\$ @TylerHarden No, I just not came up with how to generalize it, sorry. \$\endgroup\$ – Dmitry Jun 10 '15 at 18:24
  • \$\begingroup\$ @TylerHarden I've updated the answer. Now it should work not worse than yours. Please revise. \$\endgroup\$ – Dmitry Jun 10 '15 at 21:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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