Search code examples
iphonenscalendarnsdatecomponentsweekend

Finding how many times a day will appear from now until target date


I am making an iPhone count down app and need to know how many times a weekend (like Saturday and Sundays ) will happen until the date the user sets.

An example is how could I find how many weekends will occur from now (Monday, August 6th) until next week? I know the answer is 1 weekend, but I need to be able to figure this out using code.

The closest I have gotten is using the NSDateComponents and NSCalender and doing something close to the following.

NSDateComponents *weekendsLeftComponents = [calendar components:NSWeekCalendarUnit
                                                    fromDate:targetDate
                                                      toDate:[NSDate date]
                                                     options:0];

But from there I have hit the dreaded coders wall of no passing.

My problem is not exactly knowing how to go about doing this. It sounds like an Array could work, but I am afraid the Array could get rather large as some of these "things" could go as long as a couple years. (I am making an app that finds out how many days, weekends, etc until a person graduates.) Also my audience is mainly kids to teens with iPods, older ones too. I don't know how full their memory (ram) can get before running out.

Thank you so much in advance,

Alex Kafer


Solution

  • It looks like your fromDate and toDate are backwards, assuming targetDate is in the future.

    Here's how I'd solve this problem.

    Create a category on NSCalendar to count the number of times a specific weekday occurs between two dates:

    @interface NSCalendar (AlexCategory)
    
    // The number of times weekday number `weekday` (1 = Sunday, 7 = Saturday)
    // occurs between `fromDate` and `toDate`.  If `fromDate` falls on the desired
    // weekday, it is counted.  If `toDate` falls on the desired weekday, it is NOT counted.
    - (NSInteger)countOfWeekday:(NSInteger)weekday fromDate:(NSDate *)fromDate toDate:(NSDate *)toDate;
    
    @end
    

    To implement this new method, we'll start by getting the year, month, day, and weekday of the fromDate:

    @implementation NSCalendar (AlexCategory)
    
    - (NSInteger)countOfWeekday:(NSInteger)weekday fromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
        NSDateComponents *components = [self components:NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit | NSWeekdayCalendarUnit fromDate:fromDate];
    

    Next, we count how many days from fromDate to the next desired weekday:

        NSInteger daysUntilDesiredWeekday = weekday - components.weekday;
        // If fromDate is a Wednesday and weekday is Monday (for example),
        // daysUntilDesiredWeekday is negative.  Fix that.
        NSRange weekdayRange = [self minimumRangeOfUnit:NSWeekdayCalendarUnit];
        if (daysUntilDesiredWeekday < weekdayRange.location) {
            daysUntilDesiredWeekday += weekdayRange.length;
        }
    

    Note that daysUntilDesiredWeekday will be zero if fromDate falls on the desired weekday.

    Now we can create an NSDateComponents representing the first desired weekday on or after fromDate:

        NSDateComponents *firstDesiredWeekday = [[NSDateComponents alloc] init];
        firstDesiredWeekday.year = components.year;
        firstDesiredWeekday.month = components.month;
        firstDesiredWeekday.day = components.day + daysUntilDesiredWeekday;
    

    We update fromDate to be that first desired weekday, and return 0 if it's on or after toDate:

        fromDate = [self dateFromComponents:firstDesiredWeekday];
        if ([fromDate compare:toDate] != NSOrderedAscending) {
            return 0;
        }
    

    Next we count all the days (not just desired weekdays) from the updated fromDate to toDate:

        NSInteger allDaysCount = [self components:NSDayCalendarUnit
            fromDate:fromDate toDate:toDate options:0].day;
    

    We can divide that by the number of days in a week to count just the number of desired weekdays. Since we started counting from a desired weekday, any partial week remainder would also contain the desired weekday, so we need to round up:

        // Adding weekdayRange.length - 1 makes the integer division round up.
        return (allDaysCount + weekdayRange.length - 1) / weekdayRange.length;
    }
    
    @end
    

    We can test the method like this:

    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    
    NSDateComponents *components = [[NSDateComponents alloc] init];
    components.year = 2012;
    components.month = 1;
    components.day = 1;
    NSDate *fromDate = [calendar dateFromComponents:components];
    
    for (NSUInteger i = 1; i <= 365; ++i) {
        components.day = i;
        NSDate *toDate = [calendar dateFromComponents:components];
        NSLog(@"%@ to %@: %ld Mondays", fromDate, toDate, [calendar countOfWeekday:2 fromDate:fromDate toDate:toDate]);
    }
    

    The output starts like this:

    2012-08-06 16:27:05.751 tuesdays[81152:403] 0 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-01 06:00:00 +0000
    2012-08-06 16:27:05.754 tuesdays[81152:403] 0 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-02 06:00:00 +0000
    2012-08-06 16:27:05.756 tuesdays[81152:403] 1 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-03 06:00:00 +0000
    2012-08-06 16:27:05.758 tuesdays[81152:403] 1 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-04 06:00:00 +0000
    2012-08-06 16:27:05.759 tuesdays[81152:403] 1 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-05 06:00:00 +0000
    2012-08-06 16:27:05.760 tuesdays[81152:403] 1 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-06 06:00:00 +0000
    2012-08-06 16:27:05.762 tuesdays[81152:403] 1 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-07 06:00:00 +0000
    2012-08-06 16:27:05.763 tuesdays[81152:403] 1 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-08 06:00:00 +0000
    2012-08-06 16:27:05.763 tuesdays[81152:403] 1 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-09 06:00:00 +0000
    2012-08-06 16:27:05.764 tuesdays[81152:403] 2 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-10 06:00:00 +0000
    2012-08-06 16:27:05.765 tuesdays[81152:403] 2 Mondays from 2012-01-01 06:00:00 +0000 to 2012-01-11 06:00:00 +0000
    

    This looks correct according to the output of cal 1 2012:

        January 2012
    Su Mo Tu We Th Fr Sa
     1  2  3  4  5  6  7
     8  9 10 11 12 13 14
    15 16 17 18 19 20 21
    22 23 24 25 26 27 28
    29 30 31
    

    (Remember, the toDate is not counted, so there are 0 Mondays from 2012-1-1 to 2012-1-2, even though 2012-1-2 is a Monday.)