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();