Click here to Skip to main content
15,881,898 members
Articles / Programming Languages / C#
Tip/Trick

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

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
30 Mar 2013CPOL3 min read 28.2K   143   17   14
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)


Written By
CEO
Wales Wales
Born at an early age, he grew older. At the same time, his hair grew longer, and was tied up behind his head.
Has problems spelling the word "the".
Invented the portable cat-flap.
Currently, has not died yet. Or has he?

Comments and Discussions

 
BugFails for leap years and ultimo dates Pin
Gustav Brock16-Nov-15 2:13
professionalGustav Brock16-Nov-15 2:13 
BugNegative days Pin
Richard Deeming23-Oct-14 6:06
mveRichard Deeming23-Oct-14 6:06 
GeneralRe: Negative days Pin
OriginalGriff23-Oct-14 6:25
mveOriginalGriff23-Oct-14 6:25 
GeneralRe: Negative days Pin
Richard Deeming23-Oct-14 8:56
mveRichard Deeming23-Oct-14 8:56 
GeneralMy vote of 5 Pin
Maciej Los21-Aug-13 1:11
mveMaciej Los21-Aug-13 1:11 
GeneralRe: My vote of 5 Pin
OriginalGriff21-Aug-13 2:41
mveOriginalGriff21-Aug-13 2:41 
GeneralMy vote of 5 Pin
Naz_Firdouse1-Apr-13 23:54
Naz_Firdouse1-Apr-13 23:54 
QuestionAn alternative approach to Getting the age Pin
George Swan31-Mar-13 9:24
mveGeorge Swan31-Mar-13 9:24 
AnswerRe: An alternative approach to Getting the age Pin
johannesnestler27-Jul-20 3:37
johannesnestler27-Jul-20 3:37 
GeneralMy vote of 5 Pin
Thomas Daniels30-Mar-13 8:18
mentorThomas Daniels30-Mar-13 8:18 
QuestionDownload links Pin
Thomas Daniels30-Mar-13 8:14
mentorThomas Daniels30-Mar-13 8:14 
AnswerRe: Download links Pin
OriginalGriff30-Mar-13 8:33
mveOriginalGriff30-Mar-13 8:33 
GeneralRe: Download links Pin
Thomas Daniels30-Mar-13 8:34
mentorThomas Daniels30-Mar-13 8:34 
AnswerRe: Download links Pin
OriginalGriff30-Mar-13 22:16
mveOriginalGriff30-Mar-13 22:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.