Domanda

I have an array of NSDate objects, and I want to know how many entries each month has, from now through five months back.

Is this possible to do using an NSPredicate?

È stato utile?

Soluzione

A somewhat different method to Matthias's, though I don't see any good way to do this with NSPredicate either, since date arithmetic is required here.

This creates date components from each date initially and counts those using NSCountedSet. (You might be able to use a predicate on the counted set.) Then you create another NSDateComponents object describing the month and year you are interested in, and simply query the set using countForObject:

NSArray * dates = @[/* Your dates here */];

NSCalendar * cal = [NSCalendar currentCalendar];

/* Convert the dates into date components objects for month and year. */
NSMutableArray * months = [NSMutableArray array];
for( NSDate * date in dates ){

    NSDateComponents * month = [cal components:(NSMonthCalendarUnit|NSYearCalendarUnit)
                                      fromDate:date];
    [months addObject:month];
}

/* Count the unique components. */
NSCountedSet * monthsCounts = [NSCountedSet setWithArray:months];

/* Use a predicate here on the set? */

/* A components object for month arithmetic. */
NSDateComponents * monthDelta = [NSDateComponents new];

/* Get month names for the current locale. */
NSDateFormatter * formatter = [NSDateFormatter new];
NSArray * monthNames = [formatter monthSymbols];

for( NSInteger i = 0; i > -NUM_MONTHS; i-- ){

    /* Couting backwards from the current month, get a date components
     * object for each month and year.
     */
    [monthDelta setMonth:i];
    NSDateComponents * currMonth;
    currMonth = [cal components:(NSMonthCalendarUnit|NSYearCalendarUnit)
                       fromDate:[cal dateByAddingComponents:monthDelta
                                                     toDate:[NSDate date]
                                                    options:0]];
    /* Get the count from the counted set and display. */
    NSUInteger count = [monthsCounts countForObject:currMonth];
    NSLog(@"%lu dates in %@", count,
          [monthNames objectAtIndex:[currMonth month] - 1]);
}

Altri suggerimenti

You have to test the dates in your array 6 times.

First: Match all dates on or after July 1st AND before August 1st.
Second: Match all dates on or after June 1st AND before July 1st.
Third: Match all dates on or after May 1st AND before June 1st.
and so on

In a loop you create two dates. First date is the beginning of the month at midnight. Second date is exactly 1 month(!) later. Then you filter out all dates from the array that are on or after the first and before the second date. In the next iteration of the loop you make the startDate one month earlier than the previous startDate.

Lots of date calculations, but with the help of NSDateComponents it's not a big deal.

Since I'm not sure how to use a NSPredicate with NSDates in a array I used another method, but this is basically how it works:

NSDateComponents *oneMonthOffset = [[NSDateComponents alloc] init];
oneMonthOffset.month = 1;

NSDateComponents *thisMonth = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit fromDate:[NSDate date]];
thisMonth.day = 1;
NSDate *startOfCurrentMonth = [calendar dateFromComponents:thisMonth];

for (NSInteger offset = 0; offset > -5; offset--) {
    NSDateComponents *startDateOffset = [[NSDateComponents alloc] init];
    startDateOffset.month = offset;
    NSDate *startDate = [calendar dateByAddingComponents:startDateOffset toDate:startOfCurrentMonth options:0];
    NSDate *endDate = [calendar dateByAddingComponents:oneMonthOffset toDate:startDate options:0];
    NSIndexSet *matchingIndexes = [array indexesOfObjectsPassingTest:^BOOL(NSDate *date, NSUInteger idx, BOOL *stop) {
        if ([date compare:startDate] != NSOrderedAscending && [date compare:endDate] == NSOrderedAscending) {
            // date is not before startDate (i.e. is on startDate or later than startDate) and date is before endDate
            return YES;
        }
        return NO;
    }];
    NSLog(@"%d dates after %@ and before %@", [matchingIndexes count], [startDate descriptionWithLocale:[NSLocale currentLocale]], [endDate descriptionWithLocale:[NSLocale currentLocale]]);
}

If your array of dates is very big you might want to sort it first and only scan the relevant range. This code currently tests all NSDates in the array.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top