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
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.)