You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
13 KiB
370 lines
13 KiB
// Copyright (C) 2014 by Matthew York |
|
// |
|
// Permission is hereby granted, free of charge, to any |
|
// person obtaining a copy of this software and |
|
// associated documentation files (the "Software"), to |
|
// deal in the Software without restriction, including |
|
// without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the |
|
// Software, and to permit persons to whom the Software is |
|
// furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall |
|
// be included in all copies or substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
|
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
|
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
#import "DTTimePeriodCollection.h" |
|
#import "DTError.h" |
|
#import "NSDate+DateTools.h" |
|
|
|
@implementation DTTimePeriodCollection |
|
|
|
#pragma mark - Custom Init / Factory Methods |
|
/** |
|
* Initializes a new instance of DTTimePeriodCollection |
|
* |
|
* @return DTTimePeriodCollection |
|
*/ |
|
+(DTTimePeriodCollection *)collection{ |
|
return [[DTTimePeriodCollection alloc] init]; |
|
} |
|
|
|
#pragma mark - Collection Manipulation |
|
/** |
|
* Adds a time period to the reciever. |
|
* |
|
* @param period DTTimePeriod - The time period to add to the collection |
|
*/ |
|
-(void)addTimePeriod:(DTTimePeriod *)period{ |
|
if ([period isKindOfClass:[DTTimePeriod class]]) { |
|
[periods addObject:period]; |
|
|
|
//Set object's variables with updated array values |
|
[self updateVariables]; |
|
} |
|
else { |
|
[DTError throwBadTypeException:period expectedClass:[DTTimePeriod class]]; |
|
} |
|
} |
|
|
|
/** |
|
* Inserts a time period to the receiver at a given index. |
|
* |
|
* @param period DTTimePeriod - The time period to insert into the collection |
|
* @param index NSInteger - The index in the collection the time period is to be added at |
|
*/ |
|
-(void)insertTimePeriod:(DTTimePeriod *)period atIndex:(NSInteger)index{ |
|
if ([period class] != [DTTimePeriod class]) { |
|
[DTError throwBadTypeException:period expectedClass:[DTTimePeriod class]]; |
|
return; |
|
} |
|
|
|
if (index >= 0 && index < periods.count) { |
|
[periods insertObject:period atIndex:index]; |
|
|
|
//Set object's variables with updated array values |
|
[self updateVariables]; |
|
} |
|
else { |
|
[DTError throwInsertOutOfBoundsException:index array:periods]; |
|
} |
|
} |
|
|
|
/** |
|
* Removes the time period at a given index from the collection |
|
* |
|
* @param index NSInteger - The index in the collection the time period is to be removed from |
|
*/ |
|
-(void)removeTimePeriodAtIndex:(NSInteger)index{ |
|
if (index >= 0 && index < periods.count) { |
|
[periods removeObjectAtIndex:index]; |
|
|
|
//Update the object variables |
|
if (periods.count > 0) { |
|
//Set object's variables with updated array values |
|
[self updateVariables]; |
|
} |
|
else { |
|
[self setVariablesNil]; |
|
} |
|
} |
|
else { |
|
[DTError throwRemoveOutOfBoundsException:index array:periods]; |
|
} |
|
} |
|
|
|
|
|
|
|
#pragma mark - Sorting |
|
/** |
|
* Sorts the time periods in the collection by earliest start date to latest start date. |
|
*/ |
|
-(void)sortByStartAscending{ |
|
[periods sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { |
|
return [((DTTimePeriod *) obj1).StartDate compare:((DTTimePeriod *) obj2).StartDate]; |
|
}]; |
|
} |
|
|
|
/** |
|
* Sorts the time periods in the collection by latest start date to earliest start date. |
|
*/ |
|
-(void)sortByStartDescending{ |
|
[periods sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { |
|
return [((DTTimePeriod *) obj2).StartDate compare:((DTTimePeriod *) obj1).StartDate]; |
|
}]; |
|
} |
|
|
|
/** |
|
* Sorts the time periods in the collection by earliest end date to latest end date. |
|
*/ |
|
-(void)sortByEndAscending{ |
|
[periods sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { |
|
return [((DTTimePeriod *) obj1).EndDate compare:((DTTimePeriod *) obj2).EndDate]; |
|
}]; |
|
} |
|
|
|
/** |
|
* Sorts the time periods in the collection by latest end date to earliest end date. |
|
*/ |
|
-(void)sortByEndDescending{ |
|
[periods sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { |
|
return [((DTTimePeriod *) obj2).EndDate compare:((DTTimePeriod *) obj1).EndDate]; |
|
}]; |
|
} |
|
|
|
/** |
|
* Sorts the time periods in the collection by how much time they span. Sorts smallest durations to longest. |
|
*/ |
|
-(void)sortByDurationAscending{ |
|
[periods sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { |
|
if (((DTTimePeriod *) obj1).durationInSeconds < ((DTTimePeriod *) obj2).durationInSeconds) { |
|
return NSOrderedAscending; |
|
} |
|
else { |
|
return NSOrderedDescending; |
|
} |
|
return NSOrderedSame; |
|
}]; |
|
} |
|
|
|
/** |
|
* Sorts the time periods in the collection by how much time they span. Sorts longest durations to smallest. |
|
*/ |
|
-(void)sortByDurationDescending{ |
|
[periods sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { |
|
if (((DTTimePeriod *) obj1).durationInSeconds > ((DTTimePeriod *) obj2).durationInSeconds) { |
|
return NSOrderedAscending; |
|
} |
|
else { |
|
return NSOrderedDescending; |
|
} |
|
return NSOrderedSame; |
|
}]; |
|
} |
|
|
|
#pragma mark - Collection Relationship |
|
/** |
|
* Returns an instance of DTTimePeriodCollection with all the time periods in the receiver that fall inside a given time period. |
|
* Time periods of the receiver must have a start date and end date within the closed interval of the period provided to be included. |
|
* |
|
* @param period DTTimePeriod - The time period to check against the receiver's time periods. |
|
* |
|
* @return DTTimePeriodCollection |
|
*/ |
|
-(DTTimePeriodCollection *)periodsInside:(DTTimePeriod *)period{ |
|
DTTimePeriodCollection *collection = [[DTTimePeriodCollection alloc] init]; |
|
|
|
if ([period isKindOfClass:[DTTimePeriod class]]) { |
|
[periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
|
if ([((DTTimePeriod *) obj) isInside:period]) { |
|
[collection addTimePeriod:obj]; |
|
} |
|
}]; |
|
} |
|
else { |
|
[DTError throwBadTypeException:period expectedClass:[DTTimePeriod class]]; |
|
} |
|
|
|
return collection; |
|
} |
|
|
|
/** |
|
* Returns an instance of DTTimePeriodCollection with all the time periods in the receiver that intersect a given date. |
|
* Time periods of the receiver must have a start date earlier than or equal to the comparison date and an end date later than or equal to the comparison date to be included |
|
* |
|
* @param date NSDate - The date to check against the receiver's time periods |
|
* |
|
* @return DTTimePeriodCollection |
|
*/ |
|
-(DTTimePeriodCollection *)periodsIntersectedByDate:(NSDate *)date{ |
|
DTTimePeriodCollection *collection = [[DTTimePeriodCollection alloc] init]; |
|
|
|
if ([date isKindOfClass:[NSDate class]]) { |
|
[periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
|
if ([((DTTimePeriod *) obj) containsDate:date interval:DTTimePeriodIntervalClosed]) { |
|
[collection addTimePeriod:obj]; |
|
} |
|
}]; |
|
} |
|
else { |
|
[DTError throwBadTypeException:date expectedClass:[NSDate class]]; |
|
} |
|
|
|
return collection; |
|
} |
|
|
|
/** |
|
* Returns an instance of DTTimePeriodCollection with all the time periods in the receiver that intersect a given time period. |
|
* Intersection with the given time period includes other time periods that simply touch it. (i.e. one's start date is equal to another's end date) |
|
* |
|
* @param period DTTimePeriod - The time period to check against the receiver's time periods. |
|
* |
|
* @return DTTimePeriodCollection |
|
*/ |
|
-(DTTimePeriodCollection *)periodsIntersectedByPeriod:(DTTimePeriod *)period{ |
|
DTTimePeriodCollection *collection = [[DTTimePeriodCollection alloc] init]; |
|
|
|
if ([period isKindOfClass:[DTTimePeriod class]]) { |
|
[periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
|
if ([((DTTimePeriod *) obj) intersects:period]) { |
|
[collection addTimePeriod:obj]; |
|
} |
|
}]; |
|
} |
|
else { |
|
[DTError throwBadTypeException:period expectedClass:[DTTimePeriod class]]; |
|
} |
|
|
|
return collection; |
|
} |
|
|
|
/** |
|
* Returns an instance of DTTimePeriodCollection with all the time periods in the receiver that overlap a given time period. |
|
* Overlap with the given time period does NOT include other time periods that simply touch it. (i.e. one's start date is equal to another's end date) |
|
* |
|
* @param period DTTimePeriod - The time period to check against the receiver's time periods. |
|
* |
|
* @return DTTimePeriodCollection |
|
*/ |
|
-(DTTimePeriodCollection *)periodsOverlappedByPeriod:(DTTimePeriod *)period{ |
|
DTTimePeriodCollection *collection = [[DTTimePeriodCollection alloc] init]; |
|
|
|
[periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
|
if ([((DTTimePeriod *) obj) overlapsWith:period]) { |
|
[collection addTimePeriod:obj]; |
|
} |
|
}]; |
|
|
|
return collection; |
|
} |
|
|
|
/** |
|
* Returns a BOOL representing whether the receiver is equal to a given DTTimePeriodCollection. Equality requires the start and end dates to be the same, and all time periods to be the same. |
|
* |
|
* If you would like to take the order of the time periods in two collections into consideration, you may do so with the considerOrder BOOL |
|
* |
|
* @param collection DTTimePeriodCollection - The collection to compare with the receiver |
|
* @param considerOrder BOOL - Option for whether to account for the time periods order in the test for equality. YES considers order, NO does not. |
|
* |
|
* @return BOOL |
|
*/ |
|
-(BOOL)isEqualToCollection:(DTTimePeriodCollection *)collection considerOrder:(BOOL)considerOrder{ |
|
//Check class |
|
if ([collection class] != [DTTimePeriodCollection class]) { |
|
[DTError throwBadTypeException:collection expectedClass:[DTTimePeriodCollection class]]; |
|
return NO; |
|
} |
|
|
|
//Check group level characteristics for speed |
|
if (![self hasSameCharacteristicsAs:collection]) { |
|
return NO; |
|
} |
|
|
|
//Default to equality and look for inequality |
|
__block BOOL isEqual = YES; |
|
if (considerOrder) { |
|
|
|
[periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
|
if (![collection[idx] isEqualToPeriod:obj]) { |
|
isEqual = NO; |
|
*stop = YES; |
|
} |
|
}]; |
|
} |
|
else { |
|
__block DTTimePeriodCollection *collectionCopy = [collection copy]; |
|
|
|
[periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
|
__block BOOL innerMatch = NO; |
|
__block NSInteger matchIndex = 0; //We will remove matches to account for duplicates and to help speed |
|
for (int ii = 0; ii < collectionCopy.count; ii++) { |
|
if ([obj isEqualToPeriod:collectionCopy[ii]]) { |
|
innerMatch = YES; |
|
matchIndex = ii; |
|
break; |
|
} |
|
} |
|
|
|
//If there was a match found, stop |
|
if (!innerMatch) { |
|
isEqual = NO; |
|
*stop = YES; |
|
} |
|
else { |
|
[collectionCopy removeTimePeriodAtIndex:matchIndex]; |
|
} |
|
}]; |
|
} |
|
|
|
return isEqual; |
|
} |
|
|
|
#pragma mark - Helper Methods |
|
|
|
-(void)updateVariables{ |
|
//Set helper variables |
|
__block NSDate *startDate = [NSDate distantFuture]; |
|
__block NSDate *endDate = [NSDate distantPast]; |
|
[periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
|
if ([((DTTimePeriod *) obj).StartDate isEarlierThan:startDate]) { |
|
startDate = ((DTTimePeriod *) obj).StartDate; |
|
} |
|
if ([((DTTimePeriod *) obj).EndDate isLaterThan:endDate]) { |
|
endDate = ((DTTimePeriod *) obj).EndDate; |
|
} |
|
}]; |
|
|
|
//Make assignments after evaluation |
|
StartDate = startDate; |
|
EndDate = endDate; |
|
} |
|
|
|
-(void)setVariablesNil{ |
|
//Set helper variables |
|
StartDate = nil; |
|
EndDate = nil; |
|
} |
|
|
|
/** |
|
* Returns a new instance of DTTimePeriodCollection that is an exact copy of the receiver, but with differnt memory references, etc. |
|
* |
|
* @return DTTimePeriodCollection |
|
*/ |
|
-(DTTimePeriodCollection *)copy{ |
|
DTTimePeriodCollection *collection = [DTTimePeriodCollection collection]; |
|
|
|
[periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
|
[collection addTimePeriod:[obj copy]]; |
|
}]; |
|
|
|
return collection; |
|
} |
|
|
|
@end
|
|
|