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/2009I 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?开发者_开发百科
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;
}
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
}
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;
}
You could use the DayOfYear property of the DateTime objects.
if ((birthday.DayOfYear >= start.DayOfYear) && (birthday.DayOfYear <= end.DayOfYear)) {
...
}
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:
- The LowerBound has the same year as the UpperBound
- 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);
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 ) );
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;
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;
}
if (Birthday.Month >= DateRangeLower.Month && Birthday.Month <= DateRangeUpper.Month
&& Birthday.Day>= DateRangeLower.Day && Birthday.Day<= DateRangeUpper.Day) {
//Partytime...
}
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.
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!
}
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;
}
}
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;
}
精彩评论