NSDate comparison of just day & month?

Discussion in 'iOS Programming' started by dantastic, Mar 19, 2011.

  1. dantastic macrumors 6502

    dantastic

    Joined:
    Jan 21, 2011
    #1
    I want to test if a date in the past is going to have a "birthday" between 2 dates in the future. Think of it just as a birthday. What I have at the moment:

    Code:
    // NSDate myDate is a date in the past - like 27 jul 1987
    // NSDate fromDate - in the future - 20 Jul 2011.
    // NSDate toDate - in the future - 30 Jul 2011.
    
    			unsigned units = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit;
    			NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    			NSDateComponents *components = [calendar components:units fromDate:myDate];
    			[components setYear:2011]; // <- This is the hack I want to get rid of.
    			NSDate *yearCorrected = [calendar dateFromComponents:components];
    
    			if (([yearCorrected compare:fromDate] != NSOrderedAscending) && ([yearCorrected compare:toDate] != NSOrderedDescending)) {
    				NSLog(@"birthday!");
    
    
    The whole bit with splitting the date into components and tweaking the year to be this year is pretty nasty. I will have obvious problems around the new year as well even if I was to get the current year properly.

    How should I go about comparing 2 dates like this as if they were a birthday? From what I can understand using NSDate to compare I can only compare fixed time stamps.

    I have been looking in to NSCalendar for comparing as well but it seems like you can only get components out of it. Easy to get the age in years but difficult to do what I want. I have also tried to just ask the date for the month and day component and then create a new date out of it but that gives me a year of 0001.
     
  2. balamw Moderator

    balamw

    Staff Member

    Joined:
    Aug 16, 2005
    Location:
    New England
    #2
    Here's just a pseudo-code idea for this I have no idea if it will work well or not.

    the dates are bdate,fdate1,fdate2 and fdate1<fdate2.

    Code:
    Assign testdate to fdate1.year,bdate.month,bdate.day
    if testdate<fdate1, increment testdate.year
    (testdate is now the date of the birthday immediately after fdate1)
    if testdate<fdate2 you will have a birthday between fdate1 and fdate2
    else you won't
    B
     
  3. dantastic thread starter macrumors 6502

    dantastic

    Joined:
    Jan 21, 2011
    #3
    hmms.. I see what you are doing there.
    Code:
    // I create a NSDate 29 December 2011 to use as 'now' for testing. 
    // I then grab a new date one day old. another date 5 days into the future. 
    // Split into components and out of that I get 2 nsintegers 2011 & 2012.
    
    			[components setYear:fromYear];
    			NSDate *fromBDay = [calendar dateFromComponents:components];
    			[components setYear:toYear];
    			NSDate *toBDay = [calendar dateFromComponents:components];
    			
    			NSLog(@"1	Dates: %@, and: %@", fromBDay, toBDay);
    			NSLog(@"1	Check between: %@, and: %@", fromDate, toDate);
    			
    			if (([fromBDay compare:fromDate] != NSOrderedAscending) && ([toBDay compare:toDate] != NSOrderedDescending)) {
    				NSLog(@"birthday");
    
    It is a step in the right direction but I get the following output from my nslogging
    Code:
    [68961:40b] 1	Dates: 2011-03-22 00:00:00 +0000, and: 2012-03-22 00:00:00 +0000
    [68961:40b] 1	Check between: 2011-12-28 00:00:00 +0000, and: 2012-01-03 00:00:00 +0000
    [68961:40b] date out of range 22 Mar 2001
    
    [68961:40b] 1	Dates: 2011-01-02 00:00:00 +0000, and: 2012-01-02 00:00:00 +0000
    [68961:40b] 1	Check between: 2011-12-28 00:00:00 +0000, and: 2012-01-03 00:00:00 +0000
    [68961:40b] date out of range 2 Jan 2004
    
    [68961:40b] 1	Dates: 2011-12-30 00:00:00 +0000, and: 2012-12-30 00:00:00 +0000
    [68961:40b] 1	Check between: 2011-12-28 00:00:00 +0000, and: 2012-01-03 00:00:00 +0000
    [68961:40b] date out of range 30 Dec 2011
    
    I think it's the second comparison that is the problem.

    if I chenge to compare to only the first date like:
    Code:
    if (([fromBDay compare:fromDate] != NSOrderedAscending) && ([fromBDay compare:toDate] != NSOrderedDescending)) {
    
    I catch the date that is in december, but not the one that is in Jan but in the same time span:
    Code:
    [68995:40b] 1	Dates: 2011-01-02 00:00:00 +0000, and: 2012-01-02 00:00:00 +0000
    [68995:40b] 1	Check between: 2011-12-28 00:00:00 +0000, and: 2012-01-03 00:00:00 +0000
    [68995:40b] date out of range 2 Jan 2004
    
    [68995:40b] 1	Dates: 2011-12-30 00:00:00 +0000, and: 2012-12-30 00:00:00 +0000
    [68995:40b] 1	Check between: 2011-12-28 00:00:00 +0000, and: 2012-01-03 00:00:00 +0000
    [68995:40b] Ading to array: 30 Dec 2011
    
     
  4. dantastic, Mar 19, 2011
    Last edited: Mar 19, 2011

    dantastic thread starter macrumors 6502

    dantastic

    Joined:
    Jan 21, 2011
    #4
    ok, I'm not sure what is going on here now.
    Code:
    			if ([fromBDay compare:fromDate] == NSOrderedDescending) {
    				NSLog(@"%@ is before %@", fromBDay, fromDate);
    			}
    			else {
    				NSLog(@"%@ is after %@", fromBDay, fromDate);
    			}
    
    			if ([date compare:toDate] == NSOrderedAscending) {
    				NSLog(@"%@ is before %@", date, toDate);
    			}
    			else {
    				NSLog(@"%@ is after %@", date, toDate);
    			}
    
    From my understanding of reading the NSData class ref this should be correct. The second test seems OK but the first compare is just wrong!

    output:
    Code:
    [69086:40b] 1	Dates: 2011-03-22 00:00:00 +0000, and: 2012-03-22 00:00:00 +0000
    [69086:40b] 1	Check between: 2011-12-28 00:00:00 +0000, and: 2012-01-03 00:00:00 +0000
    [69086:40b] 2011-03-22 00:00:00 +0000 is after 2011-12-28 00:00:00 +0000
    [69086:40b] 2001-03-22 00:00:00 +0000 is before 2012-01-03 00:00:00 +0000
    
    [69086:40b] 1	Dates: 2011-01-02 00:00:00 +0000, and: 2012-01-02 00:00:00 +0000
    [69086:40b] 1	Check between: 2011-12-28 00:00:00 +0000, and: 2012-01-03 00:00:00 +0000
    [69086:40b] 2011-01-02 00:00:00 +0000 is after 2011-12-28 00:00:00 +0000
    [69086:40b] 2004-01-02 00:00:00 +0000 is before 2012-01-03 00:00:00 +0000
    
    [69086:40b] 1	Dates: 2011-12-30 00:00:00 +0000, and: 2012-12-30 00:00:00 +0000
    [69086:40b] 1	Check between: 2011-12-28 00:00:00 +0000, and: 2012-01-03 00:00:00 +0000
    [69086:40b] 2011-12-30 00:00:00 +0000 is before 2011-12-28 00:00:00 +0000
    [69086:40b] 2011-12-30 00:00:00 +0000 is before 2012-01-03 00:00:00 +0000
    
     
  5. dantastic, Mar 19, 2011
    Last edited: Mar 19, 2011

    dantastic thread starter macrumors 6502

    dantastic

    Joined:
    Jan 21, 2011
    #5
    ok, spottd that the first compare was the exact opposite of what I wanted - flipped that and it works for the set of dates just tested there. If I change the dummyDate from 29 Dec to use [NSDate date] it's not working for some dates.


    Code:
    ...
    	// fromDate is the earlier date I want to use for comparison.
    	NSDateComponents *components = [calendar components:units fromDate:fromDate];
    	NSInteger fromYear = [components year];
    
    	NSDate *date = [tempFormatter dateFromString:@"27 July 1987"];
    	components = [calendar components:units fromDate:date];
    	[components setYear:fromYear];
    	NSDate *yearCorrected = [calendar dateFromComponents:components];
    
    	if (([yearCorrected compare:fromDate] == NSOrderedAscending) && ([date compare:toDate] == NSOrderedAscending)) {
    		NSLog(@"It's your birthday!");
    	}
    
    testing :
    Code:
    Check between: 2011-03-18 15:56:55 +0000, and: 2011-03-24 15:56:55 +0000
    Not between:
     19 Mar 2011
     7 Jul 2009
    22 Mar 2001
    30 Dec 2011
    Match:
    2004-01-02 00:00:00 +0000   // what?!
    
     
  6. dantastic thread starter macrumors 6502

    dantastic

    Joined:
    Jan 21, 2011
    #6
    I've abandoned the NSDate compare function and I'm testing the earlierDate and laterDate and they seem to have the same problems!

    Code:
    if ([yearCorrected earlierDate:fromDate] == fromDate && [yearCorrected laterDate:toDate] == toDate){
        NSLog(@"%@ falls between %@ and %@",yearCorrected, fromDate, toDate);
    } else {
       NSLog(@"%@ does not fall between %@ and %@",yearCorrected, fromDate, toDate);
    }
    
    still, using components we bring the year up to date of the first date we compare to.
    Code:
    'now' is 29 dec 2011
    [69579:40b] 2011-07-06 23:00:00 +0000 does not fall between 2011-12-28 00:00:00 +0000 and 2012-01-03 00:00:00 +0000
    [69579:40b] 2011-03-22 00:00:00 +0000 does not fall between 2011-12-28 00:00:00 +0000 and 2012-01-03 00:00:00 +0000
    [69579:40b] 2011-01-02 00:00:00 +0000 does not fall between 2011-12-28 00:00:00 +0000 and 2012-01-03 00:00:00 +0000
    [69579:40b] 2011-12-30 00:00:00 +0000 falls between 2011-12-28 00:00:00 +0000 and 2012-01-03 00:00:00 +0000
    
    
    'now' is [NSDate date]
    [69611:40b] 2011-03-19 00:00:00 +0000 falls between 2011-03-18 16:22:53 +0000 and 2011-03-24 16:22:53 +0000
    [69611:40b] 2011-07-06 23:00:00 +0000 does not fall between 2011-03-18 16:22:53 +0000 and 2011-03-24 16:22:53 +0000
    [69611:40b] 2011-03-22 00:00:00 +0000 falls between 2011-03-18 16:22:53 +0000 and 2011-03-24 16:22:53 +0000
    [69611:40b] 2011-01-02 00:00:00 +0000 does not fall between 2011-03-18 16:22:53 +0000 and 2011-03-24 16:22:53 +0000
    [69611:40b] 2011-12-30 00:00:00 +0000 does not fall between 2011-03-18 16:22:53 +0000 and 2011-03-24 16:22:53 +0000
    
    we're getting closer.....
     
  7. dantastic thread starter macrumors 6502

    dantastic

    Joined:
    Jan 21, 2011
    #7
    finally!

    So what I'm doing here is I'm grabbing the year from the start & the finish date - if they are the same just keep on going. If they are not the same I need to know if the date I'm looking fore is in December or January. If it's in Dec set the yearlier year, if jan - set the later.

    This was painful, this should totally be a feature of NSCalendar - check birthday.

    Code:
    
    if (fromYear == toYear) {   
    				[components setYear:fromYear];
    			}
    			else {
    				if ([components month] == 12) {
    					[components setYear:fromYear];   // 2011
    				}
    				else {
    					[components setYear:toYear];  // 2012
    				}
    
    			}
    			
    			NSDate *yearCorrected = [calendar dateFromComponents:components];
    
    
     
  8. balamw, Mar 19, 2011
    Last edited: Mar 19, 2011

    balamw Moderator

    balamw

    Staff Member

    Joined:
    Aug 16, 2005
    Location:
    New England
    #8
    Here's a complete implementation of the approach I was suggesting earlier. I agree that it's not as simple as it probably could be. Most of this code goes into finding the first birthday that occurs after the fromDate. At that point you just need to check if that is before the toDate.

    Code:
    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    	
    	NSDate *bdayDate = [[NSDate alloc] initWithString:(@"1981-03-22 00:00:00 -0500")];
    	NSDate *fromDate = [[NSDate alloc] initWithString:(@"2011-03-19 00:00:00 -0500")];
        NSDate *toDate = [[NSDate alloc] initWithString:(@"2011-03-25 00:00:00 -0500")];					     
    	
    	NSCalendar *gregorian = [[NSCalendar alloc]
    							 initWithCalendarIdentifier:NSGregorianCalendar];
    	
    	unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit;
    		
    	//Get year of start date
    	NSDateComponents *fromComponents = [gregorian components:(unitFlags) fromDate:fromDate];	
    	int fromYear = [fromComponents year]; 
    	
    	// Find the birthday in the calendar year of start date (fromYear)
    	NSDateComponents *bdayComponents = [gregorian components:(unitFlags) fromDate:bdayDate];
    	[bdayComponents setYear:fromYear];
    	NSDate *testDate = [gregorian dateFromComponents:bdayComponents];
    	
            // Get next birthday id birthday is before start date in fromYear 
    	NSTimeInterval interval = [testDate timeIntervalSinceDate:fromDate];
    	if(interval<0){
    		[bdayComponents setYear:(fromYear+1)];
    		testDate = [gregorian dateFromComponents:bdayComponents];		
    	};
    	
    	//Now that we have the birthday after the start date check to see if that is before the end date
    	NSTimeInterval between = [toDate timeIntervalSinceDate:testDate];	
    	if (between > 0) NSLog(@"Birthday %@ IS between %@ and %@", testDate, fromDate, toDate);
    	else NSLog(@"Birthday %@ IS NOT between %@ and %@", testDate, fromDate, toDate);	
    	
    	[gregorian release];
    	[toDate release];
    	[fromDate release];
    	[bdayDate release];
    		
    	[pool drain];
        return 0;
    }
    
    EDIT: NOTE: Two things that would improve this. First, you should "normalize" all the dates to occur at the same time of day. In some of your examples you might have the birthday at 00:00 and the fromDate occurring later that same day and you might conclude that the birthday is out of range when it is occurring on the fromDate. Second, I should probably be checking for between >= 0 for the same reason. The birthday could fall on the toDate and I would miss it.

    B
     
  9. dantastic thread starter macrumors 6502

    dantastic

    Joined:
    Jan 21, 2011
    #9
    Ah yes. I see what you are doing there.

    In my actual app the dates are normalized already by the time they reach this function, good catch though.

    Looking at my own code today I'm thinking I must have had a 'slow' day yesterday - it didn't end up that complicated :) - no apologies to my future self in the comments! :D
     
  10. balamw Moderator

    balamw

    Staff Member

    Joined:
    Aug 16, 2005
    Location:
    New England
    #10
    We all have them. :)

    B
     

Share This Page