views:

241

answers:

8

Hi,

Sorry if this is a duplicate, but i think I've seen enough of Google for one day!

My application needs to adjust a clients current age by +0.5 if it has been 6 months since their last birthday.

The code should look something like this, but how many ticks would there be in 6 months?

if (DateTime.Today - dateOfBirth.Date > new TimeSpan(6))
        {
            adjust = 0.5M;
        }
        else
        {
            adjust = 0M;
        }

Thanks in advance

+5  A: 

EDIT: You know what, actually? Since clearly what you really need is just to display the user's age to within 6 months, this is what you should really do.

static decimal GetApproximateAge(DateTime dateOfBirth) {
    TimeSpan age = DateTime.Now - dateOfBirth;

    /* a note on the line below:
     * treating 182.5 days as equivalent to 6 months,
     * reasoning that this will provide acceptable accuracy
     * for ages below ~360 years
     * 
     * (result will be 1 day off for roughly every 4 years;
     * for a calculation of half-years to be inaccurate
     * would take 4 [years] * ~90 [days] = ~360)
     */

    // get age in units of 6 months
    // desired behavior is not to add 0.5 until
    // a full six months have elapsed since the user's last birthday --
    // using Math.Floor to ensure this
    double approxAgeInHalfYears = Math.Floor(age.TotalDays / 182.5);

    // now convert this to years
    // did it this way to restrict age to increments of 0.5
    double approxAgeInYears = approxAgeInHalfYears * 0.5;

    return Convert.ToDecimal(approxAgeInYears);
}

The above code includes a big comment explaining its own shortcomings, helpfully pointed out by David.

Now, here's yet another option. It's kind of ugly because it uses a loop; but it's also more rock-solid since it utilizes the DateTime.AddMonths method, which has the advantage of having already been tested and documented by Microsoft.

static decimal GetApproximateAge(DateTime dateOfBirth) {
    DateTime now = DateTime.Now;

    int birthYear = dateOfBirth.Year;
    int lastYear = now.Year - 1;

    // obviously, if the user's alive, he/she had a birthday last year;
    // therefore he/she is at least this old
    int minimumAgeInYears = lastYear - birthYear;

    // now the question is: how much time has passed since then?
    double actualAgeInYears = (double)minimumAgeInYears;

    // for every six months that have elapsed since the user's birthday
    // LAST year, add 0.5 to his/her age
    DateTime birthDateLastYear = new DateTime(lastYear, 1, 1)
        .AddDays(dateOfBirth.DayOfYear);

    DateTime comparisonDate = birthDateLastYear
        .AddMonths(6);

    while (comparisonDate < now) {
        actualAgeInYears += 0.5;
        comparisonDate = comparisonDate.AddMonths(6);
    }

    return Convert.ToDecimal(actualAgeInYears);
}

This idea that people have been suggesting of checking dateOfBirth.AddMonths(6) is wrong. Since dateOfBirth is a DateTime, it represents the user's birth date, not their birth day.

What you want to check is if six months have elapsed since the user's last birthday--not the date they were born. Here's one way to do that:

DateTime lastBirthDay = GetLastBirthday(dateOfBirth);

if (DateTime.Today > lastBirthDay.AddMonths(6))
{
    adjust = 0.5M;
}
else
{
    adjust = 0M;
}

DateTime GetLastBirthday(DateTime dateOfBirth)
{
    int currentYear = DateTime.Now.Year;
    int birthMonth = dateOfBirth.Month;
    int birthDay = dateOfBirth.Day;

    // if user was born on Feb 29 and this year is NOT a leap year,
    // we'll say the user's birthday this year falls on Feb 28
    if (birthMonth == 2 && birthDay == 29 && !IsLeapYear(currentYear))
        birthDay = 28;

    DateTime birthdayThisYear = new DateTime(
        currentYear,
        birthMonth,
        birthDay
    );

    if (DateTime.Today > birthdayThisYear)
        return birthdayThisYear;
    else
        return birthdayThisYear.AddYears(-1);
}

bool IsLeapYear(int year) {
    return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
}
Dan Tao
Thanks Dan, you've posted some very useful info
Longball27
You've partially fixed the Feb 29 bug, but you still have the Lotus 1-2-3 leap year bug: You've assumed that *any* year divisible by 4 is a leap year and use a Feb 29 date for those years. Years divisible by 100 are not leap years unless they are also divisible by 400. That means Feb 29, 1900 will throw an argument exception (and with birth dates this does come up, I've had to work with Lotus/Excel (Excel uses Lotus dates for backwards compatibility) not liking *living* clients who where born in the 1890s).
David
@David: Ha, wow, I honestly did not even know that about leap years. Thanks for pointing it out. (By the way, for what it's worth, I didn't downvote your answer.)
Dan Tao
I like your new solution (it gets to the heart of the matter), but I think there is a leap year bug in it too. As the person gets older, the number of leap days not taken into consideration will add up quickly (you've take 365/2). Would it be better to use 182.625 (365.25/2) instead (and maybe use Floor instead of Round) so that a 40 year old person doesn't have 10 unaccounted for days in their calculation? Unless the program is meant to calculate for dead people (historic) the deviation due to the 100/400 year skip should get rounded off.
David
@Dan Tao I jumped the gun like everyone else and answered based on what seemed to be the immediate question (how to get time period of 6 months) without seeing that the whole concept being presented was flawed, so the downvotes are all justified. Ironically I still get a reasonably good net reputation because those downvotes count so little. At least it sorts the answers on the page correctly.
David
@David: Believe it or not, I did think of that (the issue of the new solution). What I figured was basically that, since the idea is really only to get the user's age accurate to within 6 months, the inaccuracies that will creep in due to leap years will be pretty negligible for any realistic data.
Dan Tao
@David: To put it in numeric terms, my thinking was this: The value for `age` calculated in `GetApproximateAge` is going to be 1 day off for every four years. For this to affect the calculation rounding to the nearest 6 months, the inaccuracy would have to reach ~90 days; at a rate of 1 day of inaccuracy per 4 years, a person would have to be about 360 years old for the value returned to be inaccurate (and then only by 0.5). Granted, all this explanation probably belongs in comments within the method. But to me, this is an acceptable level of accuracy.
Dan Tao
+3  A: 

Why not if (dateOfBirth.Date.AddMonths(6) < DateTime.Today) instead?

David
You realize this will always return true assuming the person is at least six months old?
Dan Tao
Very good point. I think I was busy trying to ignore the fact that the specified code will adjust the age by 0.5 regardless of whether that is the correct amount (we don't know the test is being done exactly 6 months after the birthday, is 6 months really *exactly* half a year) or if the increment has been done before. From the other answers it seems this blindness was not uncommon. Maybe I should edit the answer with something more correct (of course that means I need to deal with DoB of Feb 29 - do we want 6 months since the last Feb 29, or six months since either Feb 28 or Mar 1?)
David
Sorry, i should have mentioned it was the persons LAST birthday i was interested in. Updated my question now
Longball27
@David: Good call on Feburary 29. I added a check for that in my long-winded answer.
Dan Tao
+1  A: 
        long ticks = new DateTime(0).AddMonths(6).Ticks;
        TimeSpan ts = new TimeSpan(ticks);
Nate Zaugg
A: 

I think you might be overcomplicating things:

DateTime displayDate = User.BirthDate; // User.BirthDate is a mock for however you get the birth date

if(DateTime.Now.AddMonths(-6) > displayDate)
{
    // More than 6 Months have passed, so perform your logic to add .5 years
}
Tejs
A: 

Something like this?

if (DateTime.Now.AddMonths(-6) > dateofBirth.Date)
{
    dateOfBirth = dateOfBirth.AddMonths(6);
}
Germ
+1 - There's 15 ways to skin this cat.
Tejs
`dateOfBirth.AddMonths(6);` does nothing. It just returns a value.
Dan Tao
@Germ: DateTime is immutable you can't change it, you would have to do `dateOfBirth = dateOfBirth.AddMonths(6)`
James
Good catch on my mistake
Germ
@Germ: It's still no good. Like David's answer, this will add six months to `dateOfBirth` as long as the user is at least six months old. Also, I think the idea is to add 0.5 to the user's *age*, not their birthdate!
Dan Tao
+1  A: 
if (dateOfBirth.Date.AddMonths(6) < DateTime.Today)
{
   age += 0.5;
}
Sebastian P.R. Gingter
That's also always true if you are over six months old.
Hightechrider
A: 

I think you actually want to know if it is half a year since their last birthday not six months (since months vary in length).

    int daysDiff = DateTime.Now.DayOfYear - dayofBirth.DayOfYear;
    if (daysDiff <0) daysDiff += 365;
    double adjust = daysDiff > 365/2 ? 0.5 : 0.0;
Hightechrider
A: 
DateTime today = DateTime.Today;
DateTime lastBirthday = dateOfBirth.Date.AddYears(today.Year - dateOfBirth.Year);
if (lastBirthday > today) lastBirthday = lastBirthday.AddYears(-1);

if (today > lastBirthday.AddMonths(6))
    adjust = 0.5M;
else
    adjust = 0M;
LukeH