Click here to Skip to main content
Click here to Skip to main content

Tagged as

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

, 30 Mar 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
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.

/// <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.

/// <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:

/// <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":

/// <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:

/// <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:

/// <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!

    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)

Share

About the Author

OriginalGriff
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?
Follow on   Google+

Comments and Discussions

 
BugNegative days PinprofessionalRichard Deeming23-Oct-14 7:06 
GeneralRe: Negative days PinprotectorOriginalGriff23-Oct-14 7:25 
GeneralRe: Negative days PinprofessionalRichard Deeming23-Oct-14 9:56 
GeneralMy vote of 5 PinmvpMaciej Los21-Aug-13 2:11 
GeneralRe: My vote of 5 PinprotectorOriginalGriff21-Aug-13 3:41 
GeneralMy vote of 5 PinmemberNaz_Firdouse2-Apr-13 0:54 
QuestionAn alternative approach to Getting the age PinmemberGeorge Swan31-Mar-13 10:24 
GeneralMy vote of 5 Pinmember ProgramFOX30-Mar-13 9:18 
QuestionDownload links Pinmember ProgramFOX30-Mar-13 9:14 
AnswerRe: Download links PinmvpOriginalGriff30-Mar-13 9:33 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 30 Mar 2013
Article Copyright 2013 by OriginalGriff
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid