Dan Dan - 2 months ago 15
C# Question

Format A TimeSpan With Years

I have a class with 2 date properties:

FirstDay
and
LastDay
.
LastDay
is nullable. I would like to generate a string in the format of
"x year(s) y day(s)"
. If the total years are less than 1, I would like to omit the year section. If the total days are less than 1, I would like to omit the day section. If either years or days are 0, they should say "day/year", rather than "days/years" respectively.

Examples:

2.2 years:             "2 years 73 days"

1.002738 years:   "1 year 1 day"

0.2 years:             "73 days"

2 years:                "2 years"


What I have works, but it is long:

private const decimal DaysInAYear = 365.242M;

public string LengthInYearsAndDays
{
get
{
var lastDay = this.LastDay ?? DateTime.Today;
var lengthValue = lastDay - this.FirstDay;

var builder = new StringBuilder();

var totalDays = (decimal)lengthValue.TotalDays;
var totalYears = totalDays / DaysInAYear;
var years = (int)Math.Floor(totalYears);

totalDays -= (years * DaysInAYear);
var days = (int)Math.Floor(totalDays);

Func<int, string> sIfPlural = value =>
value > 1 ? "s" : string.Empty;

if (years > 0)
{
builder.AppendFormat(
CultureInfo.InvariantCulture,
"{0} year{1}",
years,
sIfPlural(years));

if (days > 0)
{
builder.Append(" ");
}
}

if (days > 0)
{
builder.AppendFormat(
CultureInfo.InvariantCulture,
"{0} day{1}",
days,
sIfPlural(days));
}

var length = builder.ToString();
return length;
}
}


Is there a more concise way of doing this (but still readable)?

Answer

A TimeSpan doesn't have a sensible concept of "years" because it depends on the start and end point. (Months is similar - how many months are there in 29 days? Well, it depends...)

To give a shameless plug, my Noda Time project makes this really simple though:

using System;
using NodaTime;

public class Test
{
    static void Main(string[] args)
    {
        LocalDate start = new LocalDate(2010, 6, 19);
        LocalDate end = new LocalDate(2013, 4, 11);
        Period period = Period.Between(start, end,
                                       PeriodUnits.Years | PeriodUnits.Days);

        Console.WriteLine("Between {0} and {1} are {2} years and {3} days",
                          start, end, period.Years, period.Days);
    }
}

Output:

Between 19 June 2010 and 11 April 2013 are 2 years and 296 days