13,054,779 members (71,079 online)
Tip/Trick
alternative version

#### Stats

10.2K views
17 bookmarked
Posted 30 Mar 2013

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

, 30 Mar 2013
 Rate this:
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.

## 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)
{
Days += DateTime.DaysInMonth(end.Year, end.Month);
}
Months = end.Month - start.Month;
if (Months < 0)
{
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;
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"

## Share

 CEO 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?

## You may also be interested in...

 First Prev Next
 Fails for leap years and ultimo dates Gustav Brock16-Nov-15 2:13 Gustav Brock 16-Nov-15 2:13
 Negative days Richard Deeming23-Oct-14 6:06 Richard Deeming 23-Oct-14 6:06
 Re: Negative days OriginalGriff23-Oct-14 6:25 OriginalGriff 23-Oct-14 6:25
 Re: Negative days Richard Deeming23-Oct-14 8:56 Richard Deeming 23-Oct-14 8:56
 My vote of 5 Maciej Los21-Aug-13 1:11 Maciej Los 21-Aug-13 1:11
 Re: My vote of 5 OriginalGriff21-Aug-13 2:41 OriginalGriff 21-Aug-13 2:41
 My vote of 5 Naz_Firdouse1-Apr-13 23:54 Naz_Firdouse 1-Apr-13 23:54
 An alternative approach to Getting the age George Swan31-Mar-13 9:24 George Swan 31-Mar-13 9:24
 Thanks for the interesting article. It seems to me that the `GetAge` method is performing a series of subtractions where some of the numbers have different bases. The month subtraction is using base 12 and the days subtraction is using the number of days in the month before the end date as a base.Given this, it is possible to write a general method that can be used for finding the age at any granularity level of time. Something along these lines.``` private int SubtractUsingBase(int basenumber, int minuend, int subtrahend, ref bool isCarry) { int difference = minuend - subtrahend; if (isCarry) { difference -= 1; } if (difference < 0) { difference += basenumber; isCarry = true; } else { isCarry = false; }   return difference; }```It can be used like this.``` start=new DateTime(2000,10,08,18,21,19); end = new DateTime(2013, 9, 07, 17, 20, 18); bool isHasCarry = false; int seconds = this.SubtractUsingBase(60, end.Second, start.Second, ref isHasCarry); int minutes = this.SubtractUsingBase(60, end.Minute, start.Minute, ref isHasCarry); int hours = this.SubtractUsingBase(24, end.Hour, start.Hour, ref isHasCarry);   int daysBase = DateTime.DaysInMonth(end.AddMonths(-1).Year, end.AddMonths(-1).Month);   int Days = this.SubtractUsingBase(daysBase, end.Day, start.Day, ref isHasCarry); int Months = this.SubtractUsingBase(12, end.Month,start.Month, ref isHasCarry); int Years = this.SubtractUsingBase(0, end.Year, start.Year, ref isHasCarry);```
 My vote of 5 ProgramFOX30-Mar-13 8:18 ProgramFOX 30-Mar-13 8:18