tags:

views:

512

answers:

15

Given I have a birthday/anniversary DateTime, how can I determine if that date occurred during a specific date range? For example,

Birthday = 1/2/2000
Date Range = 12/25/2008 - 1/3/2009

I need a method to determine whether or not this person's birthday happened during that date range - preferably in C#.

I first went about changing the year of the birthday DateTime to match the date range, then just check if the "new" birthday DateTime is between the start and end date of the date range... but when the date range spans different years, like in my example above - I had to add a nasty if statement. Is there no better way?

+2  A: 

I'm assuming that your dates are stored in DateTime variables? If so, the comparison is pretty straight forward:

if (Birthday > DateRangeLower && Birthday < DateRangeUpper) {
    // it's your birthday!
}

You can encapsulate this in an extension method if you like:

public static bool Between(this DateTime compareDate, DateTime startDate, DateTime endDate) {
    return compareDate > startDate && compareDate < endDate;
}

then you can call it like such:

if (Birthday.Between(DateRangeLower, DateRangeUpper) {
    // it's your birthday
}

Update: If you want to ignore the birthday's year part to determine if the anniversary of the date is within the range, apply the following:

if (DateRangeLower.DayOfYear <= DateRangeUpper.DayOfYear &&  
    Birthday.DayOfYear > DateRangeLower.DayOfYear && Birthday.DayOfYear < DateRangeUpper.DayOfYear) {
  // it's your birthday
  // the days are within the date range (and the range is in a single year)
}
else if (DateRangeLower.DayOfYear > DateRangeUpper.DayOfYear &&
    Birthday.DayOfYear < DateRangeLower.DayOfYear && Birthday.DayOfYear > DateRangeUpper.DayOfYear) {
  //  it's your birthday
  //  note, we're actually checking to see if the date is outside of the
  //  original date's days to handle the case where the dates span a year end boundary
  //  this only works if the dates are not more than 1 year apart
}
Dexter
I think you mean Birthday < DateRangeUpper - and probably should be >= and <=.
Mongus Pong
I'm not sure this is what the OP is looking for. His situation involves shifting the Birthday value *into* the range (based on year) to see if it falls inside or outside the range.
LBushkin
Oops.. good spot @Mongus. @davekaro can make the comparison date inclusive if he needs by changing to >= and <= (and possibly using .Date to ignore any time parts if needed)
Dexter
@LBushkin... if the OP is not using DateTime variables for his upper and lower bounds, I'd encourage him to first parse his variables into DateTimes.. and then use this method. Maybe he's looking for something more complicated!
Dexter
You can't ignore the year component so easily though, this solution will not tell you if a person born on 25/1/2000 has a birthday between jan and feb of 2008, which is what he is asking for.
SLC
@Dexter, my variables are DateTime types - but I don't see how the comparison will work. It's like @LBushkin and @SLC said, because my "birthday" variable has a DateTime way in the past.
davekaro
@davekaro.. ok, see my update for determining anniversaries. I misread your original comment
Dexter
Finally this looks like my solution - so I assume it works now ...
tanascius
+1  A: 

You could use the DayOfYear property of the DateTime objects.

if ((birthday.DayOfYear >= start.DayOfYear) && (birthday.DayOfYear <= end.DayOfYear)) {
  ...
}
Ray
This would fall over if the range is 01/11/2008 - 01/03/2009.
Mongus Pong
true - maybe his situation will allow him to use this - otherwise, it looks like he is back to a nasty if statement. I think tanascius has the right idea.
Ray
This would fail if the person is not born before the range being tested. Check my answer for a possible solution.
João Angelo
A: 
if (Birthday.Month >= DateRangeLower.Month && Birthday.Month <= DateRangeUpper.Month
      && Birthday.Day>= DateRangeLower.Day && Birthday.Day<= DateRangeUpper.Day) {
 //Partytime...
}
chriszero
Wait, I thought this was the correct answer, but it's not - If your date range is 1st jan to 3rd of march, and your birthday is feb 6th, this code won't work.
SLC
It's not correct but it's close. You need to check month and only if Birthday.Month == DateRangeLower.Month or Birthday.Month == DateRangeUpper.Month then check day as well.
Michał Piaskowski
You both right, thanks. Little logical mistake of me. =)
chriszero
+1  A: 

Here is my solution. It uses DayOfYear to find a match. But you have to take care, if the DayOfYear of the start-date is past the DayOfYear of the end-date. I assume, that the start date is earlier than the end date:

private static bool HasBirthDay( DateTime birthday, DateTime start, DateTime end )
{
    Debug.Assert( start < end );
    if( start.DayOfYear < end.DayOfYear )
    {
        if( birthday.DayOfYear > start.DayOfYear && birthday.DayOfYear < end.DayOfYear )
        {
            return true;
        }
    }
    else
    {
        if( birthday.DayOfYear < end.DayOfYear || birthday.DayOfYear > start.DayOfYear )
        {
            return true;
        }
    }
    return false;
}

// DayOfYear(start date) > DayOfYear(end date)
var start = new DateTime( 2008, 12, 25 );
var end = new DateTime( 2009, 1, 3 );
Debug.Assert( HasBirthDay( new DateTime( 2000, 1, 2 ), start, end ) );
Debug.Assert( HasBirthDay( new DateTime( 2000, 12, 26), start, end ) );
Debug.Assert( !HasBirthDay( new DateTime( 2000, 1, 5 ), start, end ) );
Debug.Assert( !HasBirthDay( new DateTime( 2000, 12, 24 ), start, end ) );

// DayOfYear(start date) < DayOfYear(end date)
start = new DateTime( 2008, 10, 25 );
end = new DateTime( 2008, 11, 3 );
Debug.Assert( HasBirthDay( new DateTime( 2000, 10, 26 ), start, end ) );
Debug.Assert( !HasBirthDay( new DateTime( 2000, 12, 5 ), start, end ) );
Debug.Assert( !HasBirthDay( new DateTime( 2000, 1, 24 ), start, end ) );
tanascius
Is there any reason for the downvote? I'd like to know when there is a bug in this code. Please leave a comment
tanascius
+1  A: 

Updated answer to include the upper bound normalization mentioned by SLC. This should work for the cases where the person is nor born on 29/02.

DateTime birthday = new DateTime(2000, 2, 1);

DateTime min = new DateTime(2008, 12, 25);
DateTime max = new DateTime(2009, 3, 1);

DateTime nLower = new DateTime(min.Year, birthday.Month, birthday.Day);
DateTime nUpper = new DateTime(max.Year, birthday.Month, birthday.Day);

if (birthday.Year <= max.Year && 
    ((nLower >= min && nLower <= max) || (nUpper >= min && nUpper <= max)))
{
    // Happy birthday
    Console.WriteLine("Happy birthday");
}

And now a version that handles people born on the day (29/02):

public static bool IsBirthdayInRange(
    DateTime birthday, DateTime min, DateTime max)
{
    var dates = new  DateTime[] { birthday, min };
    for (int i = 0; i < dates.Length; i++)
    {
        if (dates[i].Month == 2 && dates[i].Day == 29)
        {
            dates[i] = dates[i].AddDays(-1);
        }
    }

    birthday = dates[0];
    min = dates[1];

    DateTime nLower = new DateTime(min.Year, birthday.Month, birthday.Day);
    DateTime nUpper = new DateTime(max.Year, birthday.Month, birthday.Day);

    if (birthday.Year <= max.Year &&
        ((nLower >= min && nLower <= max) || (nUpper >= min && nUpper <= max)))
    {
        return true;
    }

    return false;
}
João Angelo
I came back to post this solution, this is the way to do it, basically set your input year to have the same year as the lower bounds of the search. You may have to do it for the upper bound as well though, in the above case, if the bday is 1983 10 5 and range is 1985 10 6 to 1986 10 10 as the IF will fail on the normalized >= min part.
SLC
And what happens in a leap year? For example, when `birthday` is `29/02/1984` and `min` is `01/10/2005`?
LukeH
I don't think this will work for the Dates I provided in my original question. Using my dates, the normalized date will be 1/2/2008 which is not >= to the "min" (12/25/2008).
davekaro
@SLC, you are right, this is not enough.
João Angelo
@Luke, leap years would possible just throw an exception.
João Angelo
@davekaro, I will update the solution.
João Angelo
+1  A: 

The crux of your problem is figuring out which Year to assign to the Birthday in order to make sure that you can perform a valid range comparison.

You have two subcases relating to the range that you need to deal with:

  1. The LowerBound has the same year as the UpperBound
  2. The LowerBound has a different year from the UpperBound

EDIT: Not enough coffee. Ignore my previous answer.

You need to adjust the dates based on the month/day of the birthday you are examining.

You cannot always use the upper bound year because the birthday could fall into a month that is greater than the upper bound month. One simple alternative is to perform the check twice: once using the upper bound year and then again using the year prior. This handles the cases of year boundaries:

var birthday = DateTime.Parse( "1/2/2000" );
var lowerBound = DateTime.Parse( "12/25/2008" );
var upperBound = DateTime.Parse( "1/3/2009" );

var adjustA = new Birthday( upperBound.Year, birthday.Month, birthday.Day );
var adjustB = adjustA.AddYears( -1 );

var isInBounds = (adjustA >= lowerBound && adjustA <= upperBound) ||
                 (adjustB >= lowerBound && adjustB <= upperBound);
LBushkin
If the person is born in the year 2010 this would still say that he has a birthday in that range. I provided a possible solution by first checking only the year and then normalizing the birthday for the comparison.
João Angelo
Your DateTime constructor is wrong. You solution does not match 12/26/2000
tanascius
And what happens, for example, when `someBirthday` is `29/02/2008` and `upperBoundDate` is `25/03/2009`?
LukeH
@João Angelo: I had a bit of brain freeze on that one. Your point is correct, and I've adjusted my solution.
LBushkin
A: 

You might be be best to draw a picture for this.

The problem is fundamentally determining if exists such N such that the person's Nth birthday lies in the range or not.

You could take a baseline and do a day number calculation with a modulo, which would handle the year rollover (but leap years might cause off-by-one errors).

Another alternative which might make for a simpler representation is that since birthdays form a 1-D grid on the calendar line, for a birthday NOT to fall within the range, the range has to completely lie between the person's birthdays on successive years: i.e. NOT (BirthdayY1 < RangeStart && RangeEnd < BirthdayY2).

Typically when we did this kind of analysis it was on whole months, so it was much simpler to find all birthdays in May, for instance, to get birthday cards.

Cade Roux
A: 

Edit: I think it's fixed now.

bool AfterLowerMonth  = (birthday.Month > lower.Month) || ( birthday.Month = lower.Month && birthday.Day >= lower.Day )
bool BeforeUpperMonth  = (birthday.Month < upper.Month) || ( birthday.Month = upper.Month && birthday.Day <= upper.Day )

bool result = (lower.Year == upper.Year && AfterLowerMonth  && BeforeUpperMonth  );
result |= lower.Year == upper.Year -1 && (AfterLowerMonth  || BeforeUpperMonth );
result |= lower.Year < upper.Year -1;

return result;
Michał Piaskowski
A: 

Set the birthday to year = 2000, the from date to year = 2000, and the to date to year 2000. If the to date is before the from date, set the to date to year 2001.

After that shenanigans, from above:

if (Birthday > DateRangeLower && Birthday < DateRangeUpper) {
    // it's your birthday!
}
Dean J
A: 

Will this work!!!

for(int i = startDate.year; i <= endDate.year; i++)
{
    DateTime newBD = new DateTime(i, BD.month, BD.day);
    if((DateTime.Compare(newBD, startDate) >= 0) && (DateTime.Compare(newBD, endDate) <= 0))        
    {
        //gotcha
        break;  
    }
}
Veer
+2  A: 

Ok, here's my take

public static bool IsBirthDayInRange(DateTime birthday, DateTime start, DateTime end)
{
    DateTime temp = birthday.AddYears(start.Year - birthday.Year);

    if (temp < start)
        temp = temp.AddYears(1);

    return birthday <= end && temp >= start && temp <= end;
}
Haas
+1  A: 

I'd just convert all the dates to Epoch time, then do a straight comparison.

I found this conversion here, slightly modified

int epoch = (int)({Beginning/Ending Date} - new DateTime(1970, 1, 1)).TotalSeconds;

So your entire set of code would just be

int StartDateInEpoch = (int)(StartDate - new DateTime(1970, 1, 1)).TotalSeconds;
int EndDateInEpoch = (int)(EndDate - new DateTime(1970, 1, 1)).TotalSeconds;
int TargetDateInEpoch = (int)(TargetDate - new DateTime(1970, 1, 1)).TotalSeconds;

if (StartDateInEpoch < TargetDateInEpoch && TargetDateInEpoch <= EndDateInEpoch)
    return true;
jfawcett
A: 

A different answer, moving all dates to a specific year.

public static bool IsBirthDayInRange(DateTime birthday, DateTime start, DateTime end)
{
    // This could be any date...
    var epoch = new DateTime(1970, 1, 1);

    // Start date is always epoch, end date is epoch + range span
    DateTime endDateInEpoch = epoch.AddSeconds((end - start).TotalSeconds);
    // Move the bithday back to epoch.Year
    DateTime birthDayInEpoch = birthday.AddYears(epoch.Year - birthday.Year);

    return birthday <= end && epoch < birthDayInEpoch && birthDayInEpoch <= endDateInEpoch;
}
Haas
A: 

I think this is right

    public static bool AnniversaryBetween(this DateTime anniversary, DateTime date1, DateTime date2)
    {
        DateTime startDate = date1 < date2 ? date1 : date2;
        DateTime endDate = date1 == startDate ? date2 : date1;

        DateTime anniversaryDate = new DateTime( 
            anniversary.DayOfYear < startDate.DayOfYear? startDate.Year + 1: startDate.Year, 
            anniversary.Month, 
            anniversary.Day);

        return anniversaryDate >= startDate.Date && anniversaryDate <= endDate.Date;
    }
Seattle Leonard
A: 

This one should correctly handle leap years :

public static bool IsBirthdayInRange(DateTime birthday, DateTime from, DateTime to)
{
    if (to < from)
    {
        throw new ArgumentException("The specified range is not valid");
    }

    int year = from.Year;
    int month = birthday.Month;
    int day = birthday.Day;
    if (from.DayOfYear > to.DayOfYear && birthday.DayOfYear < from.DayOfYear)
    {
        year++;
    }
    if (month == 2 && day == 29 && !DateTime.IsLeapYear(year))
    {
       // Assuming people born on February 29 celebrate their birthday
       // one day earlier on non-leap years
       day--;
    }
    DateTime bDate = new DateTime(year, month, day);
    return bDate >= from.Date && bDate <= to.Date;
}
Thomas Levesque