Write TimeSpan in full

18

I want to write a static class (can be Extension ) to represent the value of a structure TimeSpan " in full, in Portuguese.

The idea is to always compare the current date ( Datetime.Now ) with the date that the event occurred. The result of this is TimeSpan I want to use to write Razor dates like this:

  

There are 3 seconds.

     

2 hours ago.

     

2 days and 5 hours ago.

     

A week ago.

     

There are 3 weeks and 2 days.

     

One month ago.

     

There are 3 months, 2 weeks and 4 days.

     

One year ago.

     

For 2 years and 7 months.

How could this be done in an elegant way?

    
asked by anonymous 24.08.2015 / 19:26

3 answers

16

There is a answer in SO that deals with this and was well accepted. I would probably improve something to look more elegant and still not doing exactly as you wish, but this is it:

public static String RelativeTime(this TimeSpan ts) {
    const int second = 1;
    const int minute = 60 * second;
    const int hour = 60 * minute;
    const int day = 24 * hour;
    const int month = 30 * day;
    double delta = Math.Abs(ts.TotalSeconds);
    if (delta < 1 * minute) { //melhor se escrever só "Agora há pouco"
      return "Há " + (ts.Seconds == 1 ? "um segundo" : ts.Seconds + " segundos");
    }
    if (delta < 2 * minute) {
      return "Há um minuto";
    }
    if (delta < 45 * minute) {
      return "Há " + ts.Minutes + " minutos";
    }
    if (delta < 90 * minute) {
      return "Há uma hora";
    }
    if (delta < 24 * hour) {
      return "Há " + ts.Hours + " horas";
    }
    if (delta < 48 * hour) {
      return "ontem";
    }
    if (delta < 30 * day) {
      return "Há " + ts.Days + " dias";
    }
    if (delta < 12 * month) {
      var months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
      return "Há " + (months <= 1 ? "um mês" : months + " meses");
    } else {
      var years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
      return "Há " + (years <= 1 ? "um ano" : years + " anos");
    }
}

See running on dotNetFidle .

It has some problems but solves most situations. One problem is that it does not address if the value is future. The rounding calibration is rigid, in fact, it can make several improvements in the algorithm, besides improving the style of the code.

You also have this package that helps "humanize" all of that data.

You have an alternative that considers part-time as shown in the question. The implementation does not take into account some minor offsets, plural, hours, etc., as reported in the original response in the SO : / p>

var dtNow = DateTime.Now;
var dtYesterday = DateTime.Now.AddDays(-435.0);
var ts = dtNow.Subtract(dtYesterday);

var years = ts.Days / 365; //no leap year accounting
var months = (ts.Days % 365) / 30; //naive guess at month size
var weeks = ((ts.Days % 365) % 30) / 7;
var days = (((ts.Days % 365) % 30) % 7);

var sb = new StringBuilder("Há ");
if(years > 0) {
    sb.Append(years.ToString() + " anos, ");
}
if(months > 0) {
    sb.Append(months.ToString() + " meses, ");
}
if(weeks > 0) {
    sb.Append(weeks.ToString() + " semanas, ");
}
if(days > 0) {
    sb.Append(days.ToString() + " dias.");
}
var FormattedTimeSpan = sb.ToString();
    
24.08.2015 / 19:53
14

Just curious, I implemented the @bigown response as an Extension , as follows:

public static class RelativeTimeExtensions
{
    public static String PorExtenso(this TimeSpan timeSpan)
    {
        const int SECOND = 1;
        const int MINUTE = 60 * SECOND;
        const int HOUR = 60 * MINUTE;
        const int DAY = 24 * HOUR;
        const int MONTH = 30 * DAY;

        // var ts = new TimeSpan(DateTime.UtcNow.Ticks - yourDate.Ticks);
        double delta = Math.Abs(timeSpan.TotalSeconds);

        if (delta < 1 * MINUTE)
        {
            return "Há " + (timeSpan.Seconds == 1 ? "um segundo" : timeSpan.Seconds + " segundos");
        }
        if (delta < 2 * MINUTE)
        {
            return "Há um minuto";
        }
        if (delta < 45 * MINUTE)
        {
            return "Há " + timeSpan.Minutes + " minutos";
        }
        if (delta < 90 * MINUTE)
        {
            return "Há uma hora";
        }
        if (delta < 24 * HOUR)
        {
            return "Há " + timeSpan.Hours + " horas";
        }
        if (delta < 48 * HOUR)
        {
            return "ontem";
        }
        if (delta < 30 * DAY)
        {
            return "Há " + timeSpan.Days + " dias";
        }
        if (delta < 12 * MONTH)
        {
            int months = Convert.ToInt32(Math.Floor((double)timeSpan.Days / 30));
            return "Há " + (months <= 1 ? "um mês" : months + " meses");
        }
        else
        {
            int years = Convert.ToInt32(Math.Floor((double)timeSpan.Days / 365));
            return "Há " + (years <= 1 ? "um ano" : years + " anos");
        }
    }
}

Usage (not Razor):

@((DateTime.Now - dataDeComparacao).PorExtenso())
    
24.08.2015 / 20:30
3

Just to add more content, this is my somewhat similar solution, where I pass datetime as a parameter:

public static string TimeAgo(DateTime dt)
    {
        TimeSpan span = DateTime.Now - dt;

        if (span.Days > 365)
        {
            int years = (span.Days / 365);

            if (span.Days % 365 != 0)
                years += 1;

            return String.Format("Há {0} {1} atrás",

            years, years == 1 ? "dia" : "dias");
        }
        if (span.Days > 30)
        {
            int months = (span.Days / 30);

            if (span.Days % 31 != 0)
                months += 1;

            return String.Format("Há {0} {1} atrás",

            months, months == 1 ? "mês" : "Mês");
        }
        if (span.Days > 0)
            return String.Format("Há {0} {1} Atrás",

            span.Days, span.Days == 1 ? "Dia" : "Dias");

        if (span.Hours > 0)
            return String.Format("Há {0} {1} Atrás",

            span.Hours, span.Hours == 1 ? "hora" : "horas");

        if (span.Minutes > 0)
            return String.Format("Há {0} {1} Atrás",

            span.Minutes, span.Minutes == 1 ? "minutos" : "minutos");

        if (span.Seconds > 5)
            return String.Format("Há {0} segundos atrás", span.Seconds);

        if (span.Seconds <= 5)
            return "agora";

        return string.Empty;
    }
    
31.08.2015 / 22:54