Download demo and Age ClassDownload 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.
public DateTime StartDate
{
get { return _startDate; }
set
{
_startDate = value;
if (_endDate == null)
{
_endDate = DateTime.Now;
}
GetAge(_startDate, _endDate);
}
}
public DateTime EndDate
{
get { return _endDate; }
set
{
_endDate = value;
if (_startDate == null)
{
_startDate = DateTime.Now;
}
GetAge(_startDate, _endDate);
}
}
public int Years { get; private set; }
public int Months { get; private set; }
public int Days { get; private set; }
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.
public Age(DateTime startDate) : this(startDate, DateTime.Now.Date) { }
public Age(DateTime startDate, DateTime endDate)
{
_startDate = startDate;
_endDate = endDate;
GetAge(startDate, endDate);
}
The GetAge method does the real work:
private void GetAge(DateTime start, DateTime end)
{
if (start > end)
{
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":
public Age(DateTime startDate, int years) : this(startDate, years, 0, 0) { }
public Age(DateTime startDate, int years, int months) : this(startDate, years, months, 0) { }
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:
public Age Update()
{
EndDate = DateTime.Now;
return this;
}
Override ToString and we're done:
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.