Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Working with Age: it's not the same as a TimeSpan!

5.00/5 (12 votes)
30 Mar 2013CPOL3 min read 30.4K   146  
Working with an Age (as in a persons age) is not the same as a Timespan, and there is no simple way to return an age. This provides a class to solve this.
Download demo and Age Class

Download Age Class only

Introduction 

Recently, I answered a QA question wanting to calculate age in Day, Month and Year format from the selected date and I thought "Simple - just subtract the two dates, to get a TimeSpan, and there is the info". Nope, TimeSpan doesn't support Months, or Years - so I returned it as a new DateTime object instead. Nope, that's wrong as well (as ProgamFOX[^] kindly and correctly pointed out)  - a DateTime can't have a zero Month, but an age can.

Then Sergey Alexandrovich Kryukov[^] added this:

"There are no solution to this ill-posed problem at all. You should understand that months or years cannot be considered as the units of duration at all. Calendar is non-periodic, which is important in this case!" 

Which got me thinking. I know what he means, but... I have an age, you have an age. Aren't they real? Can't they be represented? Yes - they can!

Background

Fortunately, there is a solution, but let's just look at why it's a problem:

The age of a bottle of wine depends on when it was manufactured, and the year in which the question is asked. Your age depends on when you were born and the date today - it will be different tomorrow. And as Adrian Mole will tell you, you can indeed be "13 and 3/4" or 13 years, 9 months old!  And legally, there is a difference between (for example) 17 years, 11 months, and 30 days, and 18 years, zero months and zero days. The former may mean you are a child, the latter may mean you are to be treated as an adult, capable of making your own decisions - and accepting the punishment for them if necessary.

We have two fundamental Time handlers in .NET:

  • DateTime which holds an absolute point in time.
  • TimeSpan which holds a relative but fixed duration.

But an age is neither of these, nor is it a combination of one of each. The age of something is a fixed item which varies according to a second fixed point - it is not a fixed value in itself. It starts at a fixed point - the birthday, or commencement date - but it is only relevant when you add a second fixed point that the first can be compared to.  

So, it was obvious I would need to do the job properly, and create an Age class to handle it. 

The Age Class

It's pretty simple - it has a start and end date property, and Years, Months, and Days properties to read them out. I also added a number of constructor overloads to make it a bit more flexible.

The minimum constructor takes a DateTime as the start date, and uses the current date and time as the end date. It works out the number of years, months and days from them.

C#
/// <summary>
/// Start of age period
/// Birthdate for example
/// </summary>
public DateTime StartDate
    {
    get { return _startDate; }
    set
        {
        _startDate = value;
        if (_endDate == null)
            {
            _endDate = DateTime.Now;
            }
        GetAge(_startDate, _endDate);
        }
    }
/// <summary>
/// End of age period
/// Today for example
/// </summary>
public DateTime EndDate
    {
    get { return _endDate; }
    set
        {
        _endDate = value;
        if (_startDate == null)
            {
            _startDate = DateTime.Now;
            }
        GetAge(_startDate, _endDate);
        }
    }
/// <summary>
/// Years between start and end dates
/// </summary>
public int Years { get; private set; }
/// <summary>
/// Months between start and end dates, not included in whole years
/// </summary>
public int Months { get; private set; }
/// <summary>
/// Days between start and end dates, not included in whole months
/// </summary>
public int Days { get; private set; }
/// <summary>
/// Is this an appointment?
/// Returns true if the end date is before the start date.
/// </summary>
public bool IsAppointment
    {
    get { return EndDate < StartDate; }
    }

The IsAppointment property is to allow for cases when the start date is after the end date without having to return negative number of years, months and / or days which would complicate using the class.

I did consider making the StartDate property setter private, but decided that was unnecessarily limiting if I did allow the end date to be changed. In the end I went for public getter and setter, with the setter re-calculating the age.

C#
/// <summary>
/// Constructs an Age between the given start date and now
/// </summary>
/// <param name="startDate">Date to start: for example, birthdate</param>
public Age(DateTime startDate) : this(startDate, DateTime.Now.Date) { }
/// <summary>
/// Constructs an Age between the given start date and end date
/// </summary>
/// <param name="startDate">Date to start: for example, birthdate</param>
/// <param name="endDate">Date to end: for example, today</param>
public Age(DateTime startDate, DateTime endDate)
    {
    _startDate = startDate;
    _endDate = endDate;
    GetAge(startDate, endDate);
    }
The GetAge method does the real work:

C#
/// <summary>
/// Calculate the age
/// </summary>
/// <param name="start">Date to start: for example, birthdate</param>
/// <param name="end">Date to end: for example, today</param>
private void GetAge(DateTime start, DateTime end)
    {
    if (start > end)
        {
        // Rationalize the inputs!
        DateTime temp = start;
        start = end;
        end = temp;
        }
    Days = end.Day - start.Day;
    if (Days < 0)
        {
        end = end.AddMonths(-1);
        Days += DateTime.DaysInMonth(end.Year, end.Month);
        }
    Months = end.Month - start.Month;
    if (Months < 0)
        {
        end = end.AddYears(-1);
        Months += 12;
        }
    Years = end.Year - start.Year;
    }
I also added a couple of constructors to make it easier to say "this date will be your nth birthday":

C#
/// <summary>
/// Constructs an Age based on a start date and a given number of years
/// </summary>
/// <param name="startDate">Date to start: for example, birthdate</param>
/// <param name="years">Number of years for the age</param>
public Age(DateTime startDate, int years) : this(startDate, years, 0, 0) { }
/// <summary>
/// Constructs an Age based on a start date and a given number of years and months
/// </summary>
/// <param name="startDate">Date to start: for example, birthdate</param>
/// <param name="years">Number of years for the age</param>
/// <param name="months">Number of months for the age</param>
public Age(DateTime startDate, int years, int months) : this(startDate, years, months, 0) { }
/// <summary>
/// Constructs an Age based on a start date and a given number of years, months and days
/// </summary>
/// <param name="startDate">Date to start: for example, birthdate</param>
/// <param name="years">Number of years for the age</param>
/// <param name="months">Number of months for the age</param>
/// <param name="days">Number of days for the age</param>
public Age(DateTime startDate, int years, int months, int days)
    {
    _startDate = startDate;
    _endDate = _startDate.AddYears(years).AddMonths(months).AddDays(days);
    GetAge(_startDate, _endDate);
    }
A public Update method to change the end date to the current:

C#
/// <summary>
/// Updates the Age for the current date.
/// </summary>
/// <returns></returns>
public Age Update()
    {
    EndDate = DateTime.Now;
    return this;
    }
Override ToString and we're done:

C#
/// <summary>
/// Returns a human readable form of the Age
/// </summary>
/// <returns></returns>
public override string ToString()
    {
    return string.Format("{0}{1} years, {2} months and {3} days", IsAppointment ? "will be " : "", Years, Months, Days);
    }

Using the code 

Simple!

C#
DateTime dtBirth = new DateTime(1917, 2, 24);
Age age = new Age(dtBirth);
Console.WriteLine("You are {0} years, {1} months and {2} days old", age.Years, age.Months, age.Days);
Console.WriteLine(age);
Prints:
You are 96 years, 1 months and 6 days old
96 years, 1 months and 6 days 

History

Original version

30 March 2013

  • Fix typo: "tsit" for "this"
  • Fix downloads - both links pointed to the same file.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)