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.
371 lines
13 KiB
371 lines
13 KiB
9 years ago
|
// 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
|