diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 78e0352..0f18abd 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -7,11 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 203834332DEEF65A004401C8 /* FMDB in Frameworks */ = {isa = PBXBuildFile; productRef = 203834322DEEF65A004401C8 /* FMDB */; }; 2063EF612DEE6B790080FEBC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2063EF602DEE6B790080FEBC /* Foundation.framework */; }; 2063EF632DEE6B830080FEBC /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2063EF622DEE6B830080FEBC /* Security.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 2038342F2DEEF5CC004401C8 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 2063EF602DEE6B790080FEBC /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 2063EF622DEE6B830080FEBC /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; B9DA97B12DC1472C00A4DA20 /* RingAppKMP.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RingAppKMP.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -48,6 +50,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 203834332DEEF65A004401C8 /* FMDB in Frameworks */, 2063EF632DEE6B830080FEBC /* Security.framework in Frameworks */, 2063EF612DEE6B790080FEBC /* Foundation.framework in Frameworks */, ); @@ -59,6 +62,7 @@ 2063EF5F2DEE6B790080FEBC /* Frameworks */ = { isa = PBXGroup; children = ( + 2038342F2DEEF5CC004401C8 /* libsqlite3.tbd */, 2063EF622DEE6B830080FEBC /* Security.framework */, 2063EF602DEE6B790080FEBC /* Foundation.framework */, ); @@ -104,6 +108,7 @@ ); name = iosApp; packageProductDependencies = ( + 203834322DEEF65A004401C8 /* FMDB */, ); productName = iosApp; productReference = B9DA97B12DC1472C00A4DA20 /* RingAppKMP.app */; @@ -133,6 +138,9 @@ ); mainGroup = B9DA97A82DC1472C00A4DA20; minimizedProjectReferenceProxies = 1; + packageReferences = ( + 203834312DEEF65A004401C8 /* XCRemoteSwiftPackageReference "fmdb" */, + ); preferredProjectObjectVersion = 77; productRefGroup = B9DA97B22DC1472C00A4DA20 /* Products */; projectDirPath = ""; @@ -315,7 +323,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = NLHG3FFX4L; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -342,8 +350,9 @@ "$(PROJECT_DIR)/iosApp/Libs", ); OTHER_CFLAGS = ( - "-DSQLITE_HAS_CODEC-DSQLITE_TEMP_STORE=3-DSQLCIPHER_CRYPTO_CC-DN", - DEBUG, + "-DSQLITE_TEMP_STORE=3", + "-DSQLCIPHER_CRYPTO_CC", + "-DNDEBUG", ); OTHER_LDFLAGS = "-Objc"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -362,7 +371,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = NLHG3FFX4L; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -384,8 +393,9 @@ "$(PROJECT_DIR)/iosApp/Libs", ); OTHER_CFLAGS = ( - "-DSQLITE_HAS_CODEC-DSQLITE_TEMP_STORE=3-DSQLCIPHER_CRYPTO_CC-DN", - DEBUG, + "-DSQLITE_TEMP_STORE=3", + "-DSQLCIPHER_CRYPTO_CC", + "-DNDEBUG", ); OTHER_LDFLAGS = "-Objc"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -416,6 +426,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 203834312DEEF65A004401C8 /* XCRemoteSwiftPackageReference "fmdb" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ccgus/fmdb"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.7.12; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 203834322DEEF65A004401C8 /* FMDB */ = { + isa = XCSwiftPackageProductDependency; + package = 203834312DEEF65A004401C8 /* XCRemoteSwiftPackageReference "fmdb" */; + productName = FMDB; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = B9DA97A92DC1472C00A4DA20 /* Project object */; } diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..2c124f9 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "edf512815619ed6bb2e67b3bdb38a134d5e8f6b3f3f79c05028ab9cbffc0efd2", + "pins" : [ + { + "identity" : "fmdb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ccgus/fmdb", + "state" : { + "revision" : "1227a3fa2b9916bfd75fe380eb45cd210e69e251", + "version" : "2.7.12" + } + } + ], + "version" : 3 +} diff --git a/iosApp/iosApp/Libs/DateTools/DTConstants.h b/iosApp/iosApp/Libs/DateTools/DTConstants.h new file mode 100644 index 0000000..b9fcccf --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTConstants.h @@ -0,0 +1,35 @@ +// 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 + +FOUNDATION_EXPORT const long long SECONDS_IN_YEAR; +FOUNDATION_EXPORT const NSInteger SECONDS_IN_MONTH_28; +FOUNDATION_EXPORT const NSInteger SECONDS_IN_MONTH_29; +FOUNDATION_EXPORT const NSInteger SECONDS_IN_MONTH_30; +FOUNDATION_EXPORT const NSInteger SECONDS_IN_MONTH_31; +FOUNDATION_EXPORT const NSInteger SECONDS_IN_WEEK; +FOUNDATION_EXPORT const NSInteger SECONDS_IN_DAY; +FOUNDATION_EXPORT const NSInteger SECONDS_IN_HOUR; +FOUNDATION_EXPORT const NSInteger SECONDS_IN_MINUTE; +FOUNDATION_EXPORT const NSInteger MILLISECONDS_IN_DAY; +#import "DTError.h" \ No newline at end of file diff --git a/iosApp/iosApp/Libs/DateTools/DTConstants.m b/iosApp/iosApp/Libs/DateTools/DTConstants.m new file mode 100644 index 0000000..2320dea --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTConstants.m @@ -0,0 +1,33 @@ +// 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 "DTConstants.h" +const long long SECONDS_IN_YEAR = 31556900; +const NSInteger SECONDS_IN_MONTH_28 = 2419200; +const NSInteger SECONDS_IN_MONTH_29 = 2505600; +const NSInteger SECONDS_IN_MONTH_30 = 2592000; +const NSInteger SECONDS_IN_MONTH_31 = 2678400; +const NSInteger SECONDS_IN_WEEK = 604800; +const NSInteger SECONDS_IN_DAY = 86400; +const NSInteger SECONDS_IN_HOUR = 3600; +const NSInteger SECONDS_IN_MINUTE = 60; +const NSInteger MILLISECONDS_IN_DAY = 86400000; \ No newline at end of file diff --git a/iosApp/iosApp/Libs/DateTools/DTError.h b/iosApp/iosApp/Libs/DateTools/DTError.h new file mode 100644 index 0000000..0fff9dc --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTError.h @@ -0,0 +1,38 @@ +// 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 + +#pragma mark - Domain +extern NSString *const DTErrorDomain; + +#pragma mark - Status Codes +static const NSUInteger DTInsertOutOfBoundsException = 0; +static const NSUInteger DTRemoveOutOfBoundsException = 1; +static const NSUInteger DTBadTypeException = 2; + +@interface DTError : NSObject + ++(void)throwInsertOutOfBoundsException:(NSInteger)index array:(NSArray *)array; ++(void)throwRemoveOutOfBoundsException:(NSInteger)index array:(NSArray *)array; ++(void)throwBadTypeException:(id)obj expectedClass:(Class)classType; +@end diff --git a/iosApp/iosApp/Libs/DateTools/DTError.m b/iosApp/iosApp/Libs/DateTools/DTError.m new file mode 100644 index 0000000..f2b6715 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTError.m @@ -0,0 +1,72 @@ +// 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 "DTError.h" + +#pragma mark - Domain +NSString *const DTErrorDomain = @"com.mattyork.dateTools"; + +@implementation DTError + ++(void)throwInsertOutOfBoundsException:(NSInteger)index array:(NSArray *)array{ + //Handle possible zero bounds + NSInteger arrayUpperBound = (array.count == 0)? 0:array.count; + + //Create info for error + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedString(@"Operation was unsuccessful.", nil), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Attempted to insert DTTimePeriod at index %ld but the group is of size [0...%ld].", (long)index, (long)arrayUpperBound],NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Please try an index within the bounds or the group.", nil)}; + + //Handle Error + NSError *error = [NSError errorWithDomain:DTErrorDomain code:DTInsertOutOfBoundsException userInfo:userInfo]; + [self printErrorWithCallStack:error]; +} + ++(void)throwRemoveOutOfBoundsException:(NSInteger)index array:(NSArray *)array{ + //Handle possible zero bounds + NSInteger arrayUpperBound = (array.count == 0)? 0:array.count; + + //Create info for error + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedString(@"Operation was unsuccessful.", nil), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Attempted to remove DTTimePeriod at index %ld but the group is of size [0...%ld].", (long)index, (long)arrayUpperBound],NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Please try an index within the bounds of the group.", nil)}; + + //Handle Error + NSError *error = [NSError errorWithDomain:DTErrorDomain code:DTRemoveOutOfBoundsException userInfo:userInfo]; + [self printErrorWithCallStack:error]; +} + ++(void)throwBadTypeException:(id)obj expectedClass:(Class)classType{ + //Create info for error + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedString(@"Operation was unsuccessful.", nil), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Attempted to insert object of class %@ when expecting object of class %@.", NSStringFromClass([obj class]), NSStringFromClass(classType)],NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Please try again by inserting a DTTimePeriod object.", nil)}; + + //Handle Error + NSError *error = [NSError errorWithDomain:DTErrorDomain code:DTBadTypeException userInfo:userInfo]; + [self printErrorWithCallStack:error]; +} + ++(void)printErrorWithCallStack:(NSError *)error{ + //Print error + NSLog(@"%@", error); + + //Print call stack + for (NSString *symbol in [NSThread callStackSymbols]) { + NSLog(@"\n\n %@", symbol); + } +} +@end diff --git a/iosApp/iosApp/Libs/DateTools/DTTimePeriod.h b/iosApp/iosApp/Libs/DateTools/DTTimePeriod.h new file mode 100644 index 0000000..aa1ff8e --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTTimePeriod.h @@ -0,0 +1,123 @@ +// 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 + + +typedef NS_ENUM(NSUInteger, DTTimePeriodRelation){ + DTTimePeriodRelationAfter, + DTTimePeriodRelationStartTouching, + DTTimePeriodRelationStartInside, + DTTimePeriodRelationInsideStartTouching, + DTTimePeriodRelationEnclosingStartTouching, + DTTimePeriodRelationEnclosing, + DTTimePeriodRelationEnclosingEndTouching, + DTTimePeriodRelationExactMatch, + DTTimePeriodRelationInside, + DTTimePeriodRelationInsideEndTouching, + DTTimePeriodRelationEndInside, + DTTimePeriodRelationEndTouching, + DTTimePeriodRelationBefore, + DTTimePeriodRelationNone //One or more of the dates does not exist +}; + +typedef NS_ENUM(NSUInteger, DTTimePeriodSize) { + DTTimePeriodSizeSecond, + DTTimePeriodSizeMinute, + DTTimePeriodSizeHour, + DTTimePeriodSizeDay, + DTTimePeriodSizeWeek, + DTTimePeriodSizeMonth, + DTTimePeriodSizeYear +}; + +typedef NS_ENUM(NSUInteger, DTTimePeriodInterval) { + DTTimePeriodIntervalOpen, + DTTimePeriodIntervalClosed +}; + +typedef NS_ENUM(NSUInteger, DTTimePeriodAnchor) { + DTTimePeriodAnchorStart, + DTTimePeriodAnchorCenter, + DTTimePeriodAnchorEnd +}; + +@interface DTTimePeriod : NSObject + +/** + * The start date for a DTTimePeriod representing the starting boundary of the time period + */ +@property (nonatomic,strong) NSDate *StartDate; + +/** + * The end date for a DTTimePeriod representing the ending boundary of the time period + */ +@property (nonatomic,strong) NSDate *EndDate; + +#pragma mark - Custom Init / Factory Methods +-(instancetype)initWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate; ++(instancetype)timePeriodWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate; ++(instancetype)timePeriodWithSize:(DTTimePeriodSize)size startingAt:(NSDate *)date; ++(instancetype)timePeriodWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount startingAt:(NSDate *)date; ++(instancetype)timePeriodWithSize:(DTTimePeriodSize)size endingAt:(NSDate *)date; ++(instancetype)timePeriodWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount endingAt:(NSDate *)date; ++(instancetype)timePeriodWithAllTime; + +#pragma mark - Time Period Information +-(BOOL)hasStartDate; +-(BOOL)hasEndDate; +-(BOOL)isMoment; +-(double)durationInYears; +-(double)durationInWeeks; +-(double)durationInDays; +-(double)durationInHours; +-(double)durationInMinutes; +-(double)durationInSeconds; + +#pragma mark - Time Period Relationship +-(BOOL)isEqualToPeriod:(DTTimePeriod *)period; +-(BOOL)isInside:(DTTimePeriod *)period; +-(BOOL)contains:(DTTimePeriod *)period; +-(BOOL)overlapsWith:(DTTimePeriod *)period; +-(BOOL)intersects:(DTTimePeriod *)period; +-(DTTimePeriodRelation)relationToPeriod:(DTTimePeriod *)period; +-(NSTimeInterval)gapBetween:(DTTimePeriod *)period; + +#pragma mark - Date Relationships +-(BOOL)containsDate:(NSDate *)date interval:(DTTimePeriodInterval)interval; + +#pragma mark - Period Manipulation +#pragma mark Shifts +-(void)shiftEarlierWithSize:(DTTimePeriodSize)size; +-(void)shiftEarlierWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount; +-(void)shiftLaterWithSize:(DTTimePeriodSize)size; +-(void)shiftLaterWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount; + +#pragma mark Lengthen / Shorten +-(void)lengthenWithAnchorDate:(DTTimePeriodAnchor)anchor size:(DTTimePeriodSize)size; +-(void)lengthenWithAnchorDate:(DTTimePeriodAnchor)anchor size:(DTTimePeriodSize)size amount:(NSInteger)amount; +-(void)shortenWithAnchorDate:(DTTimePeriodAnchor)anchor size:(DTTimePeriodSize)size; +-(void)shortenWithAnchorDate:(DTTimePeriodAnchor)anchor size:(DTTimePeriodSize)size amount:(NSInteger)amount; + +#pragma mark - Helper Methods +-(DTTimePeriod *)copy; +@end diff --git a/iosApp/iosApp/Libs/DateTools/DTTimePeriod.m b/iosApp/iosApp/Libs/DateTools/DTTimePeriod.m new file mode 100644 index 0000000..364288f --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTTimePeriod.m @@ -0,0 +1,642 @@ +// 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 "DTTimePeriod.h" +#import "NSDate+DateTools.h" + +@interface DTTimePeriod () + +@end + + +@implementation DTTimePeriod + +#pragma mark - Custom Init / Factory Methods +/** + * Initializes an instance of DTTimePeriod from a given start and end date + * + * @param startDate NSDate - Desired start date + * @param endDate NSDate - Desired end date + * + * @return DTTimePeriod - new instance + */ +-(instancetype)initWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate{ + if (self = [super init]) { + self.StartDate = startDate; + self.EndDate = endDate; + } + + return self; +} + +/** + * Returns a new instance of DTTimePeriod from a given start and end date + * + * @param startDate NSDate - Desired start date + * @param endDate NSDate - Desired end date + * + * @return DTTimePeriod - new instance + */ ++(instancetype)timePeriodWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate{ + return [[self.class alloc] initWithStartDate:startDate endDate:endDate]; +} + +/** + * Returns a new instance of DTTimePeriod that starts on the provided start date + * and is of the size provided + * + * @param size DTTimePeriodSize - Desired size of the new time period + * @param date NSDate - Desired start date of the new time period + * + * @return DTTimePeriod - new instance + */ ++(instancetype)timePeriodWithSize:(DTTimePeriodSize)size startingAt:(NSDate *)date{ + return [[self.class alloc] initWithStartDate:date endDate:[DTTimePeriod dateWithAddedTime:size amount:1 baseDate:date]]; +} + +/** + * Returns a new instance of DTTimePeriod that starts on the provided start date + * and is of the size provided. The amount represents a multipler to the size (e.g. "2 weeks" or "4 years") + * + * @param size DTTimePeriodSize - Desired size of the new time period + * @param amount NSInteger - Desired multiplier of the size provided + * @param date NSDate - Desired start date of the new time period + * + * @return DTTimePeriod - new instance + */ ++(instancetype)timePeriodWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount startingAt:(NSDate *)date{ + return [[self.class alloc] initWithStartDate:date endDate:[DTTimePeriod dateWithAddedTime:size amount:amount baseDate:date]]; +} + +/** + * Returns a new instance of DTTimePeriod that ends on the provided end date + * and is of the size provided + * + * @param size DTTimePeriodSize - Desired size of the new time period + * @param date NSDate - Desired end date of the new time period + * + * @return DTTimePeriod - new instance + */ ++(instancetype)timePeriodWithSize:(DTTimePeriodSize)size endingAt:(NSDate *)date{ + return [[self.class alloc] initWithStartDate:[DTTimePeriod dateWithSubtractedTime:size amount:1 baseDate:date] endDate:date]; +} + +/** + * Returns a new instance of DTTimePeriod that ends on the provided end date + * and is of the size provided. The amount represents a multipler to the size (e.g. "2 weeks" or "4 years") + * + * @param size DTTimePeriodSize - Desired size of the new time period + * @param amount NSInteger - Desired multiplier of the size provided + * @param date NSDate - Desired end date of the new time period + * + * @return DTTimePeriod - new instance + */ ++(instancetype)timePeriodWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount endingAt:(NSDate *)date{ + return [[self.class alloc] initWithStartDate:[DTTimePeriod dateWithSubtractedTime:size amount:amount baseDate:date] endDate:date]; +} + +/** + * Returns a new instance of DTTimePeriod that represents the largest time period available. + * The start date is in the distant past and the end date is in the distant future. + * + * @return DTTimePeriod - new instance + */ ++(instancetype)timePeriodWithAllTime{ + return [[self.class alloc] initWithStartDate:[NSDate distantPast] endDate:[NSDate distantFuture]]; +} + +/** + * Method serving the various factory methods as well as a few others. + * Returns a date with time added to a given base date. Includes multiplier amount. + * + * @param size DTTimePeriodSize - Desired size of the new time period + * @param amount NSInteger - Desired multiplier of the size provided + * @param date NSDate - Desired end date of the new time period + * + * @return NSDate - new instance + */ ++(NSDate *)dateWithAddedTime:(DTTimePeriodSize)size amount:(NSInteger)amount baseDate:(NSDate *)date{ + switch (size) { + case DTTimePeriodSizeSecond: + return [date dateByAddingSeconds:amount]; + break; + case DTTimePeriodSizeMinute: + return [date dateByAddingMinutes:amount]; + break; + case DTTimePeriodSizeHour: + return [date dateByAddingHours:amount]; + break; + case DTTimePeriodSizeDay: + return [date dateByAddingDays:amount]; + break; + case DTTimePeriodSizeWeek: + return [date dateByAddingWeeks:amount]; + break; + case DTTimePeriodSizeMonth: + return [date dateByAddingMonths:amount]; + break; + case DTTimePeriodSizeYear: + return [date dateByAddingYears:amount]; + break; + default: + break; + } + + return date; +} + +/** + * Method serving the various factory methods as well as a few others. + * Returns a date with time subtracted from a given base date. Includes multiplier amount. + * + * @param size DTTimePeriodSize - Desired size of the new time period + * @param amount NSInteger - Desired multiplier of the size provided + * @param date NSDate - Desired end date of the new time period + * + * @return NSDate - new instance + */ ++(NSDate *)dateWithSubtractedTime:(DTTimePeriodSize)size amount:(NSInteger)amount baseDate:(NSDate *)date{ + switch (size) { + case DTTimePeriodSizeSecond: + return [date dateBySubtractingSeconds:amount]; + break; + case DTTimePeriodSizeMinute: + return [date dateBySubtractingMinutes:amount]; + break; + case DTTimePeriodSizeHour: + return [date dateBySubtractingHours:amount]; + break; + case DTTimePeriodSizeDay: + return [date dateBySubtractingDays:amount]; + break; + case DTTimePeriodSizeWeek: + return [date dateBySubtractingWeeks:amount]; + break; + case DTTimePeriodSizeMonth: + return [date dateBySubtractingMonths:amount]; + break; + case DTTimePeriodSizeYear: + return [date dateBySubtractingYears:amount]; + break; + default: + break; + } + + return date; +} + +#pragma mark - Time Period Information +/** + * Returns a boolean representing whether the receiver's StartDate exists + * Returns YES if StartDate is not nil, otherwise NO + * + * @return BOOL + */ +-(BOOL)hasStartDate { + return (self.StartDate)? YES:NO; +} + +/** + * Returns a boolean representing whether the receiver's EndDate exists + * Returns YES if EndDate is not nil, otherwise NO + * + * @return BOOL + */ +-(BOOL)hasEndDate { + return (self.EndDate)? YES:NO; +} + +/** + * Returns a boolean representing whether the receiver is a "moment", that is the start and end dates are the same. + * Returns YES if receiver is a moment, otherwise NO + * + * @return BOOL + */ +-(BOOL)isMoment{ + if (self.StartDate && self.EndDate) { + if ([self.StartDate isEqualToDate:self.EndDate]) { + return YES; + } + } + + return NO; +} + +/** + * Returns the duration of the receiver in years + * + * @return NSInteger + */ +-(double)durationInYears { + if (self.StartDate && self.EndDate) { + return [self.StartDate yearsEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in weeks + * + * @return double + */ +-(double)durationInWeeks { + if (self.StartDate && self.EndDate) { + return [self.StartDate weeksEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in days + * + * @return double + */ +-(double)durationInDays { + if (self.StartDate && self.EndDate) { + return [self.StartDate daysEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in hours + * + * @return double + */ +-(double)durationInHours { + if (self.StartDate && self.EndDate) { + return [self.StartDate hoursEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in minutes + * + * @return double + */ +-(double)durationInMinutes { + if (self.StartDate && self.EndDate) { + return [self.StartDate minutesEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in seconds + * + * @return double + */ +-(double)durationInSeconds { + if (self.StartDate && self.EndDate) { + return [self.StartDate secondsEarlierThan:self.EndDate]; + } + + return 0; +} + +#pragma mark - Time Period Relationship +/** + * Returns a BOOL representing whether the receiver's start and end dates exatcly match a given time period + * Returns YES if the two periods are the same, otherwise NO + * + * @param period DTTimePeriod - Time period to compare to receiver + * + * @return BOOL + */ +-(BOOL)isEqualToPeriod:(DTTimePeriod *)period{ + if ([self.StartDate isEqualToDate:period.StartDate] && [self.EndDate isEqualToDate:period.EndDate]) { + return YES; + } + return NO; +} + +/** + * Returns a BOOL representing whether the receiver's start and end dates exatcly match a given time period or is contained within them + * Returns YES if the receiver is inside the given time period, otherwise NO + * + * @param period DTTimePeriod - Time period to compare to receiver + * + * @return BOOL + */ +-(BOOL)isInside:(DTTimePeriod *)period{ + if ([period.StartDate isEarlierThanOrEqualTo:self.StartDate] && [period.EndDate isLaterThanOrEqualTo:self.EndDate]) { + return YES; + } + return NO; +} + +/** + * Returns a BOOL representing whether the given time period's start and end dates exatcly match the receivers' or is contained within them + * Returns YES if the receiver is inside the given time period, otherwise NO + * + * @param period DTTimePeriod - Time period to compare to receiver + * + * @return BOOL + */ +-(BOOL)contains:(DTTimePeriod *)period{ + if ([self.StartDate isEarlierThanOrEqualTo:period.StartDate] && [self.EndDate isLaterThanOrEqualTo:period.EndDate]) { + return YES; + } + return NO; +} + +/** + * Returns a BOOL representing whether the receiver and the given time period overlap. + * This covers all space they share, minus instantaneous space (i.e. one's start date equals another's end date) + * Returns YES if they overlap, otherwise NO + * + * @param period DTTimePeriod - Time period to compare to receiver + * + * @return BOOL + */ +-(BOOL)overlapsWith:(DTTimePeriod *)period{ + //Outside -> Inside + if ([period.StartDate isEarlierThan:self.StartDate] && [period.EndDate isLaterThan:self.StartDate]) { + return YES; + } + //Enclosing + else if ([period.StartDate isLaterThanOrEqualTo:self.StartDate] && [period.EndDate isEarlierThanOrEqualTo:self.EndDate]){ + return YES; + } + //Inside -> Out + else if([period.StartDate isEarlierThan:self.EndDate] && [period.EndDate isLaterThan:self.EndDate]){ + return YES; + } + return NO; +} + +/** + * Returns a BOOL representing whether the receiver and the given time period overlap. + * This covers all space they share, including instantaneous space (i.e. one's start date equals another's end date) + * Returns YES if they overlap, otherwise NO + * + * @param period DTTimePeriod - Time period to compare to receiver + * + * @return BOOL + */ +-(BOOL)intersects:(DTTimePeriod *)period{ + //Outside -> Inside + if ([period.StartDate isEarlierThan:self.StartDate] && [period.EndDate isLaterThanOrEqualTo:self.StartDate]) { + return YES; + } + //Enclosing + else if ([period.StartDate isLaterThanOrEqualTo:self.StartDate] && [period.EndDate isEarlierThanOrEqualTo:self.EndDate]){ + return YES; + } + //Inside -> Out + else if([period.StartDate isEarlierThanOrEqualTo:self.EndDate] && [period.EndDate isLaterThan:self.EndDate]){ + return YES; + } + return NO; +} + +/** + * Returns the relationship of the receiver to a given time period + * + * @param period DTTimePeriod - Time period to compare to receiver + * + * @return DTTimePeriodRelation + */ +-(DTTimePeriodRelation)relationToPeriod:(DTTimePeriod *)period{ + + //Make sure that all start and end points exist for comparison + if (self.StartDate && self.EndDate && period.StartDate && period.EndDate) { + //Make sure time periods are of positive durations + if ([self.StartDate isEarlierThan:self.EndDate] && [period.StartDate isEarlierThan:period.EndDate]) { + + //Make comparisons + if ([period.EndDate isEarlierThan:self.StartDate]) { + return DTTimePeriodRelationAfter; + } + else if ([period.EndDate isEqualToDate:self.StartDate]){ + return DTTimePeriodRelationStartTouching; + } + else if ([period.StartDate isEarlierThan:self.StartDate] && [period.EndDate isEarlierThan:self.EndDate]){ + return DTTimePeriodRelationStartInside; + } + else if ([period.StartDate isEqualToDate:self.StartDate] && [period.EndDate isLaterThan:self.EndDate]){ + return DTTimePeriodRelationInsideStartTouching; + } + else if ([period.StartDate isEqualToDate:self.StartDate] && [period.EndDate isEarlierThan:self.EndDate]){ + return DTTimePeriodRelationEnclosingStartTouching; + } + else if ([period.StartDate isLaterThan:self.StartDate] && [period.EndDate isEarlierThan:self.EndDate]){ + return DTTimePeriodRelationEnclosing; + } + else if ([period.StartDate isLaterThan:self.StartDate] && [period.EndDate isEqualToDate:self.EndDate]){ + return DTTimePeriodRelationEnclosingEndTouching; + } + else if ([period.StartDate isEqualToDate:self.StartDate] && [period.EndDate isEqualToDate:self.EndDate]){ + return DTTimePeriodRelationExactMatch; + } + else if ([period.StartDate isEarlierThan:self.StartDate] && [period.EndDate isLaterThan:self.EndDate]){ + return DTTimePeriodRelationInside; + } + else if ([period.StartDate isEarlierThan:self.StartDate] && [period.EndDate isEqualToDate:self.EndDate]){ + return DTTimePeriodRelationInsideEndTouching; + } + else if ([period.StartDate isEarlierThan:self.EndDate] && [period.EndDate isLaterThan:self.EndDate]){ + return DTTimePeriodRelationEndInside; + } + else if ([period.StartDate isEqualToDate:self.EndDate] && [period.EndDate isLaterThan:self.EndDate]){ + return DTTimePeriodRelationEndTouching; + } + else if ([period.StartDate isLaterThan:self.EndDate]){ + return DTTimePeriodRelationBefore; + } + } + } + + return DTTimePeriodRelationNone; +} + +/** + * Returns the gap in seconds between the receiver and provided time period + * Returns 0 if the time periods intersect, otherwise returns the gap between. + * + * @param period <#period description#> + * + * @return <#return value description#> + */ +-(NSTimeInterval)gapBetween:(DTTimePeriod *)period{ + if ([self.EndDate isEarlierThan:period.StartDate]) { + return ABS([self.EndDate timeIntervalSinceDate:period.StartDate]); + } + else if ([period.EndDate isEarlierThan:self.StartDate]){ + return ABS([period.EndDate timeIntervalSinceDate:self.StartDate]); + } + + return 0; +} + +#pragma mark - Date Relationships +/** + * Returns a BOOL representing whether the provided date is contained in the receiver. + * + * @param date NSDate - Date to evaluate + * @param interval DTTimePeriodInterval representing evaluation type (Closed includes StartDate and EndDate in evaluation, Open does not) + * + * @return <#return value description#> + */ +-(BOOL)containsDate:(NSDate *)date interval:(DTTimePeriodInterval)interval{ + if (interval == DTTimePeriodIntervalOpen) { + if ([self.StartDate isEarlierThan:date] && [self.EndDate isLaterThan:date]) { + return YES; + } + else { + return NO; + } + } + else if (interval == DTTimePeriodIntervalClosed){ + if ([self.StartDate isEarlierThanOrEqualTo:date] && [self.EndDate isLaterThanOrEqualTo:date]) { + return YES; + } + else { + return NO; + } + } + + return NO; +} + +#pragma mark - Period Manipulation +/** + * Shifts the StartDate and EndDate earlier by a given size amount + * + * @param size DTTimePeriodSize - Desired shift size + */ +-(void)shiftEarlierWithSize:(DTTimePeriodSize)size{ + [self shiftEarlierWithSize:size amount:1]; +} + +/** + * Shifts the StartDate and EndDate earlier by a given size amount. Amount multiplies size. + * + * @param size DTTimePeriodSize - Desired shift size + * @param amount NSInteger - Multiplier of size (i.e. "2 weeks" or "4 years") + */ +-(void)shiftEarlierWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount{ + self.StartDate = [DTTimePeriod dateWithSubtractedTime:size amount:amount baseDate:self.StartDate]; + self.EndDate = [DTTimePeriod dateWithSubtractedTime:size amount:amount baseDate:self.EndDate]; +} + +/** + * Shifts the StartDate and EndDate later by a given size amount + * + * @param size DTTimePeriodSize - Desired shift size + */ +-(void)shiftLaterWithSize:(DTTimePeriodSize)size{ + [self shiftLaterWithSize:size amount:1]; +} + +/** + * Shifts the StartDate and EndDate later by a given size amount. Amount multiplies size. + * + * @param size DTTimePeriodSize - Desired shift size + * @param amount NSInteger - Multiplier of size (i.e. "2 weeks" or "4 years") + */ +-(void)shiftLaterWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount{ + self.StartDate = [DTTimePeriod dateWithAddedTime:size amount:amount baseDate:self.StartDate]; + self.EndDate = [DTTimePeriod dateWithAddedTime:size amount:amount baseDate:self.EndDate]; +} + +#pragma mark Lengthen / Shorten +/** + * Lengthens the receiver by a given amount, anchored by a provided point + * + * @param anchor DTTimePeriodAnchor - Anchor point for the lengthen (the date that stays the same) + * @param size DTTimePeriodSize - Desired lenghtening size + */ +-(void)lengthenWithAnchorDate:(DTTimePeriodAnchor)anchor size:(DTTimePeriodSize)size{ + [self lengthenWithAnchorDate:anchor size:size amount:1]; +} +/** + * Lengthens the receiver by a given amount, anchored by a provided point. Amount multiplies size. + * + * @param anchor DTTimePeriodAnchor - Anchor point for the lengthen (the date that stays the same) + * @param size DTTimePeriodSize - Desired lenghtening size + * @param amount NSInteger - Multiplier of size (i.e. "2 weeks" or "4 years") + */ +-(void)lengthenWithAnchorDate:(DTTimePeriodAnchor)anchor size:(DTTimePeriodSize)size amount:(NSInteger)amount{ + switch (anchor) { + case DTTimePeriodAnchorStart: + self.EndDate = [DTTimePeriod dateWithAddedTime:size amount:amount baseDate:self.EndDate]; + break; + case DTTimePeriodAnchorCenter: + self.StartDate = [DTTimePeriod dateWithSubtractedTime:size amount:amount/2 baseDate:self.StartDate]; + self.EndDate = [DTTimePeriod dateWithAddedTime:size amount:amount/2 baseDate:self.EndDate]; + break; + case DTTimePeriodAnchorEnd: + self.StartDate = [DTTimePeriod dateWithSubtractedTime:size amount:amount baseDate:self.StartDate]; + break; + default: + break; + } +} + +/** + * Shortens the receiver by a given amount, anchored by a provided point + * + * @param anchor DTTimePeriodAnchor - Anchor point for the shorten (the date that stays the same) + * @param size DTTimePeriodSize - Desired shortening size + */ +-(void)shortenWithAnchorDate:(DTTimePeriodAnchor)anchor size:(DTTimePeriodSize)size{ + [self shortenWithAnchorDate:anchor size:size amount:1]; +} + +/** + * Shortens the receiver by a given amount, anchored by a provided point. Amount multiplies size. + * + * @param anchor DTTimePeriodAnchor - Anchor point for the shorten (the date that stays the same) + * @param size DTTimePeriodSize - Desired shortening size + * @param amount NSInteger - Multiplier of size (i.e. "2 weeks" or "4 years") + */ +-(void)shortenWithAnchorDate:(DTTimePeriodAnchor)anchor size:(DTTimePeriodSize)size amount:(NSInteger)amount{ + switch (anchor) { + case DTTimePeriodAnchorStart: + self.EndDate = [DTTimePeriod dateWithSubtractedTime:size amount:amount baseDate:self.EndDate]; + break; + case DTTimePeriodAnchorCenter: + self.StartDate = [DTTimePeriod dateWithAddedTime:size amount:amount/2 baseDate:self.StartDate]; + self.EndDate = [DTTimePeriod dateWithSubtractedTime:size amount:amount/2 baseDate:self.EndDate]; + break; + case DTTimePeriodAnchorEnd: + self.StartDate = [DTTimePeriod dateWithAddedTime:size amount:amount baseDate:self.StartDate]; + break; + default: + break; + } +} + +#pragma mark - Helper Methods +-(DTTimePeriod *)copy{ + DTTimePeriod *period = [DTTimePeriod timePeriodWithStartDate:[NSDate dateWithTimeIntervalSince1970:self.StartDate.timeIntervalSince1970] endDate:[NSDate dateWithTimeIntervalSince1970:self.EndDate.timeIntervalSince1970]]; + return period; +} + +@end diff --git a/iosApp/iosApp/Libs/DateTools/DTTimePeriodChain.h b/iosApp/iosApp/Libs/DateTools/DTTimePeriodChain.h new file mode 100644 index 0000000..21ef0b8 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTTimePeriodChain.h @@ -0,0 +1,49 @@ +// 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 +#import "DTTimePeriodGroup.h" + +@interface DTTimePeriodChain : DTTimePeriodGroup { + DTTimePeriod *First; + DTTimePeriod *Last; +} + +@property (nonatomic, readonly) DTTimePeriod *First; +@property (nonatomic, readonly) DTTimePeriod *Last; + +#pragma mark - Custom Init / Factory Chain ++(DTTimePeriodChain *)chain; + +#pragma mark - Chain Existence Manipulation +-(void)addTimePeriod:(DTTimePeriod *)period; +-(void)insertTimePeriod:(DTTimePeriod *)period atInedx:(NSInteger)index; +-(void)removeTimePeriodAtIndex:(NSInteger)index; +-(void)removeLatestTimePeriod; +-(void)removeEarliestTimePeriod; + +#pragma mark - Chain Relationship +-(BOOL)isEqualToChain:(DTTimePeriodChain *)chain; + +#pragma mark - Updates +-(void)updateVariables; +@end diff --git a/iosApp/iosApp/Libs/DateTools/DTTimePeriodChain.m b/iosApp/iosApp/Libs/DateTools/DTTimePeriodChain.m new file mode 100644 index 0000000..c33dae0 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTTimePeriodChain.m @@ -0,0 +1,218 @@ +// 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 "DTTimePeriodChain.h" +#import "DTError.h" + +@interface DTTimePeriodChain () + +@end + +@implementation DTTimePeriodChain + +#pragma mark - Custom Init / Factory Chain ++(DTTimePeriodChain *)chain{ + return [[DTTimePeriodChain alloc] init]; +} + +#pragma mark - Chain Existence Manipulation +-(void)addTimePeriod:(DTTimePeriod *)period{ + if ([period class] != [DTTimePeriod class]) { + [DTError throwBadTypeException:period expectedClass:[DTTimePeriod class]]; + return; + } + + if (periods) { + if (periods.count > 0) { + //Create a modified period to be added based on size of passed in period + DTTimePeriod *modifiedPeriod = [DTTimePeriod timePeriodWithSize:DTTimePeriodSizeSecond amount:period.durationInSeconds startingAt:[periods[periods.count - 1] EndDate]]; + + //Add object to periods array + [periods addObject:modifiedPeriod]; + } + else { + //Add object to periods array + [periods addObject:period]; + } + } + else { + //Create new periods array + periods = [NSMutableArray array]; + + //Add object to periods array + [periods addObject:period]; + } + + //Set object's variables with updated array values + [self updateVariables]; +} + +-(void)insertTimePeriod:(DTTimePeriod *)period atInedx:(NSInteger)index{ + if ([period class] != [DTTimePeriod class]) { + [DTError throwBadTypeException:period expectedClass:[DTTimePeriod class]]; + return; + } + + //Make sure the index is within the operable bounds of the periods array + if (index == 0) { + //Update bounds of period to make it fit in chain + DTTimePeriod *modifiedPeriod = [DTTimePeriod timePeriodWithSize:DTTimePeriodSizeSecond amount:period.durationInSeconds endingAt:[periods[0] EndDate]]; + + //Insert the updated object at the beginning of the periods array + [periods insertObject:modifiedPeriod atIndex:0]; + } + else if (index > 0 && index < periods.count) { + + //Shift time periods later if they fall after new period + [periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + //Shift later + if (idx >= index) { + [((DTTimePeriod *) obj) shiftLaterWithSize:DTTimePeriodSizeSecond amount:period.durationInSeconds]; + } + }]; + + //Update bounds of period to make it fit in chain + DTTimePeriod *modifiedPeriod = [DTTimePeriod timePeriodWithSize:DTTimePeriodSizeSecond amount:period.durationInSeconds startingAt:[periods[index - 1] EndDate]]; + + //Insert the updated object at the beginning of the periods array + [periods insertObject:modifiedPeriod atIndex:index]; + + //Set object's variables with updated array values + [self updateVariables]; + } + else { + [DTError throwInsertOutOfBoundsException:index array:periods]; + } +} + +-(void)removeTimePeriodAtIndex:(NSInteger)index{ + //Make sure the index is within the operable bounds of the periods array + if (index >= 0 && index < periods.count) { + DTTimePeriod *period = periods[index]; + + //Shift time periods later if they fall after new period + [periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + //Shift earlier + if (idx > index) { + [((DTTimePeriod *) obj) shiftEarlierWithSize:DTTimePeriodSizeSecond amount:period.durationInSeconds]; + } + }]; + + //Remove object + [periods removeObjectAtIndex:index]; + + //Set object's variables with updated array values + [self updateVariables]; + } + else { + [DTError throwRemoveOutOfBoundsException:index array:periods]; + } +} +-(void)removeLatestTimePeriod{ + if (periods.count > 0) { + [periods removeLastObject]; + + //Update the object variables + if (periods.count > 0) { + //Set object's variables with updated array values + [self updateVariables]; + } + else { + [self setVariablesNil]; + } + } +} +-(void)removeEarliestTimePeriod{ + if (periods > 0) { + //Shift time periods earlier + [periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + //Shift earlier to account for removal of first element in periods array + [((DTTimePeriod *) obj) shiftEarlierWithSize:DTTimePeriodSizeSecond amount:[periods[0] durationInSeconds]]; + }]; + + //Remove first period + [periods removeObjectAtIndex:0]; + + //Update the object variables + if (periods.count > 0) { + //Set object's variables with updated array values + [self updateVariables]; + } + else { + [self setVariablesNil]; + } + } +} + +#pragma mark - Chain Relationship +-(BOOL)isEqualToChain:(DTTimePeriodChain *)chain{ + //Check class + if ([chain class] != [DTTimePeriodChain class]) { + [DTError throwBadTypeException:chain expectedClass:[DTTimePeriodChain class]]; + return NO; + } + + //Check group level characteristics for speed + if (![self hasSameCharacteristicsAs:chain]) { + return NO; + } + + //Check whole chain + __block BOOL isEqual = YES; + [periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if (![chain[idx] isEqualToPeriod:obj]) { + isEqual = NO; + *stop = YES; + } + }]; + return isEqual; +} + +#pragma mark - Getters + +-(DTTimePeriod *)First{ + return First; +} + +-(DTTimePeriod *)Last{ + return Last; +} + +#pragma mark - Helper Methods + +-(void)updateVariables{ + //Set helper variables + StartDate = [periods[0] StartDate]; + EndDate = [periods[periods.count - 1] EndDate]; + First = periods[0]; + Last = periods[periods.count -1]; +} + +-(void)setVariablesNil{ + //Set helper variables + StartDate = nil; + EndDate = nil; + First = nil; + Last = nil; +} + +@end diff --git a/iosApp/iosApp/Libs/DateTools/DTTimePeriodCollection.h b/iosApp/iosApp/Libs/DateTools/DTTimePeriodCollection.h new file mode 100644 index 0000000..f3d4737 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTTimePeriodCollection.h @@ -0,0 +1,56 @@ +// 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 +#import "DTTimePeriodGroup.h" + +@interface DTTimePeriodCollection : DTTimePeriodGroup + +#pragma mark - Custom Init / Factory Methods ++(DTTimePeriodCollection *)collection; + +#pragma mark - Collection Manipulation +-(void)addTimePeriod:(DTTimePeriod *)period; +-(void)insertTimePeriod:(DTTimePeriod *)period atIndex:(NSInteger)index; +-(void)removeTimePeriodAtIndex:(NSInteger)index; + +#pragma mark - Sorting +-(void)sortByStartAscending; +-(void)sortByStartDescending; +-(void)sortByEndAscending; +-(void)sortByEndDescending; +-(void)sortByDurationAscending; +-(void)sortByDurationDescending; + +#pragma mark - Collection Relationship +-(DTTimePeriodCollection *)periodsInside:(DTTimePeriod *)period; +-(DTTimePeriodCollection *)periodsIntersectedByDate:(NSDate *)date; +-(DTTimePeriodCollection *)periodsIntersectedByPeriod:(DTTimePeriod *)period; +-(DTTimePeriodCollection *)periodsOverlappedByPeriod:(DTTimePeriod *)period; +-(BOOL)isEqualToCollection:(DTTimePeriodCollection *)collection considerOrder:(BOOL)considerOrder; + +#pragma mark - Helper Methods +-(DTTimePeriodCollection *)copy; + +#pragma mark - Updates +-(void)updateVariables; +@end diff --git a/iosApp/iosApp/Libs/DateTools/DTTimePeriodCollection.m b/iosApp/iosApp/Libs/DateTools/DTTimePeriodCollection.m new file mode 100644 index 0000000..ff62175 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTTimePeriodCollection.m @@ -0,0 +1,370 @@ +// 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 diff --git a/iosApp/iosApp/Libs/DateTools/DTTimePeriodGroup.h b/iosApp/iosApp/Libs/DateTools/DTTimePeriodGroup.h new file mode 100644 index 0000000..ac8f528 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTTimePeriodGroup.h @@ -0,0 +1,62 @@ +// 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 +#import "DTTimePeriod.h" + +@interface DTTimePeriodGroup : NSObject { +@protected + NSMutableArray *periods; + NSDate *StartDate; + NSDate *EndDate; +} + +@property (nonatomic, readonly) NSDate *StartDate; +@property (nonatomic, readonly) NSDate *EndDate; + +//Here we will use object subscripting to help create the illusion of an array +- (id)objectAtIndexedSubscript:(NSUInteger)index; //getter +- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index; //setter + +#pragma mark - Group Info +-(double)durationInYears; +-(double)durationInWeeks; +-(double)durationInDays; +-(double)durationInHours; +-(double)durationInMinutes; +-(double)durationInSeconds; +-(NSDate *)StartDate; +-(NSDate *)EndDate; +-(NSInteger)count; + +#pragma mark - Chain Time Manipulation +-(void)shiftEarlierWithSize:(DTTimePeriodSize)size; +-(void)shiftEarlierWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount; +-(void)shiftLaterWithSize:(DTTimePeriodSize)size; +-(void)shiftLaterWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount; + +#pragma mark - Comparison +-(BOOL)hasSameCharacteristicsAs:(DTTimePeriodGroup *)group; + +#pragma mark - Updates +-(void)updateVariables; +@end diff --git a/iosApp/iosApp/Libs/DateTools/DTTimePeriodGroup.m b/iosApp/iosApp/Libs/DateTools/DTTimePeriodGroup.m new file mode 100644 index 0000000..cdf0bdd --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DTTimePeriodGroup.m @@ -0,0 +1,234 @@ +// 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 "DTTimePeriodGroup.h" +#import "NSDate+DateTools.h" + +@interface DTTimePeriodGroup () + +@end + +@implementation DTTimePeriodGroup + +-(id) init +{ + if (self = [super init]) { + periods = [[NSMutableArray alloc] init]; + } + + return self; +} + +- (id)objectAtIndexedSubscript:(NSUInteger)index +{ + return periods[index]; +} + +- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index { + periods[index] = obj; +} + +#pragma mark - Group Info +/** + * Returns the duration of the receiver in years + * + * @return NSInteger + */ +-(double)durationInYears { + if (self.StartDate && self.EndDate) { + return [self.StartDate yearsEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in weeks + * + * @return double + */ +-(double)durationInWeeks { + if (self.StartDate && self.EndDate) { + return [self.StartDate weeksEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in days + * + * @return double + */ +-(double)durationInDays { + if (self.StartDate && self.EndDate) { + return [self.StartDate daysEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in hours + * + * @return double + */ +-(double)durationInHours { + if (self.StartDate && self.EndDate) { + return [self.StartDate hoursEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in minutes + * + * @return double + */ +-(double)durationInMinutes { + if (self.StartDate && self.EndDate) { + return [self.StartDate minutesEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the duration of the receiver in seconds + * + * @return double + */ +-(double)durationInSeconds { + if (self.StartDate && self.EndDate) { + return [self.StartDate secondsEarlierThan:self.EndDate]; + } + + return 0; +} + +/** + * Returns the NSDate representing the earliest date in the DTTimePeriodGroup (or subclass) + * + * @return NSDate + */ +-(NSDate *)StartDate{ + return StartDate; +} + +/** + * Returns the NSDate representing the latest date in the DTTimePeriodGroup (or subclass) + * + * @return NSDate + */ +-(NSDate *)EndDate{ + return EndDate; +} + +/** + * The total number of DTTimePeriods in the group + * + * @return NSInteger + */ +-(NSInteger)count{ + return periods.count; +} + +/** + * Returns a BOOL if the receiver and the comparison group have the same metadata (i.e. number of periods, start & end date, etc.) + * Returns YES if they share the same characteristics, otherwise NO + * + * @param group The group to compare with the receiver + * + * @return BOOL + */ +-(BOOL)hasSameCharacteristicsAs:(DTTimePeriodGroup *)group{ + //Check characteristics first for speed + if (group.count != self.count) { + return NO; + } + else if (!group.StartDate && !group.EndDate && !self.StartDate && !self.EndDate){ + return YES; + } + else if (![group.StartDate isEqualToDate:self.StartDate] || ![group.EndDate isEqualToDate:self.EndDate]){ + return NO; + } + + return YES; +} + +#pragma mark - Chain Time Manipulation +/** + * Shifts all the time periods in the collection to an earlier date by the given size + * + * @param size DTTimePeriodSize - The desired size of the shift + */ +-(void)shiftEarlierWithSize:(DTTimePeriodSize)size{ + [self shiftEarlierWithSize:size amount:1]; +} + +/** + * Shifts all the time periods in the collection to an earlier date by the given size and amount. + * The amount acts as a multiplier to the size (i.e. "2 weeks" or "4 years") + * + * @param size DTTimePeriodSize - The desired size of the shift + * @param amount NSInteger - Multiplier for the size + */ +-(void)shiftEarlierWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount{ + if (periods) { + [periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [((DTTimePeriod *)obj) shiftEarlierWithSize:size amount:amount]; + }]; + + [self updateVariables]; + } +} + +/** + * Shifts all the time periods in the collection to a later date by the given size + * + * @param size DTTimePeriodSize - The desired size of the shift + */ +-(void)shiftLaterWithSize:(DTTimePeriodSize)size{ + [self shiftLaterWithSize:size amount:1]; +} + +/** + * Shifts all the time periods in the collection to an later date by the given size and amount. + * The amount acts as a multiplier to the size (i.e. "2 weeks" or "4 years") + * + * @param size DTTimePeriodSize - The desired size of the shift + * @param amount NSInteger - Multiplier for the size + */ +-(void)shiftLaterWithSize:(DTTimePeriodSize)size amount:(NSInteger)amount{ + if (periods) { + [periods enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [((DTTimePeriod *)obj) shiftLaterWithSize:size amount:amount]; + }]; + + [self updateVariables]; + } +} + +#pragma mark - Updates +-(void)updateVariables{} +@end diff --git a/iosApp/iosApp/Libs/DateTools/DateTools.h b/iosApp/iosApp/Libs/DateTools/DateTools.h new file mode 100644 index 0000000..406ab42 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/DateTools.h @@ -0,0 +1,29 @@ +// 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 "DTConstants.h" +#import "DTError.h" +#import "NSDate+DateTools.h" +#import "DTTimePeriod.h" +#import "DTTimePeriodGroup.h" +#import "DTTimePeriodCollection.h" +#import "DTTimePeriodChain.h" \ No newline at end of file diff --git a/iosApp/iosApp/Libs/DateTools/NSDate+DateTools.h b/iosApp/iosApp/Libs/DateTools/NSDate+DateTools.h new file mode 100644 index 0000000..07b9184 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/NSDate+DateTools.h @@ -0,0 +1,194 @@ +// 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. + +#ifndef DateToolsLocalizedStrings + +#ifdef SPM +#define DateToolsLocalizedStrings(key) \ +NSLocalizedStringFromTableInBundle(key, @"DateTools", [NSBundle bundleWithPath:[[SWIFTPM_MODULE_BUNDLE resourcePath] stringByAppendingPathComponent:@"DateTools.bundle"]], nil) +#else +#define DateToolsLocalizedStrings(key) \ +NSLocalizedStringFromTableInBundle(key, @"DateTools", [NSBundle bundleWithPath:[[[NSBundle bundleForClass:[DTError class]] resourcePath] stringByAppendingPathComponent:@"DateTools.bundle"]], nil) +#endif + +#endif + +#import +#import "DTConstants.h" + +@interface NSDate (DateTools) + +#pragma mark - Time Ago ++ (NSString*)timeAgoSinceDate:(NSDate*)date; ++ (NSString*)shortTimeAgoSinceDate:(NSDate*)date; ++ (NSString *)weekTimeAgoSinceDate:(NSDate *)date; + +- (NSString*)timeAgoSinceNow; +- (NSString *)shortTimeAgoSinceNow; +- (NSString *)weekTimeAgoSinceNow; + +- (NSString *)timeAgoSinceDate:(NSDate *)date; +- (NSString *)timeAgoSinceDate:(NSDate *)date numericDates:(BOOL)useNumericDates; +- (NSString *)timeAgoSinceDate:(NSDate *)date numericDates:(BOOL)useNumericDates numericTimes:(BOOL)useNumericTimes; + + +- (NSString *)shortTimeAgoSinceDate:(NSDate *)date; +- (NSString *)weekTimeAgoSinceDate:(NSDate *)date; + + +#pragma mark - Date Components Without Calendar +- (NSInteger)era; +- (NSInteger)year; +- (NSInteger)month; +- (NSInteger)day; +- (NSInteger)hour; +- (NSInteger)minute; +- (NSInteger)second; +- (NSInteger)weekday; +- (NSInteger)weekdayOrdinal; +- (NSInteger)quarter; +- (NSInteger)weekOfMonth; +- (NSInteger)weekOfYear; +- (NSInteger)yearForWeekOfYear; +- (NSInteger)daysInMonth; +- (NSInteger)dayOfYear; +-(NSInteger)daysInYear; +-(BOOL)isInLeapYear; +- (BOOL)isToday; +- (BOOL)isTomorrow; +-(BOOL)isYesterday; +- (BOOL)isWeekend; +-(BOOL)isSameDay:(NSDate *)date; ++ (BOOL)isSameDay:(NSDate *)date asDate:(NSDate *)compareDate; + +#pragma mark - Date Components With Calendar + + +- (NSInteger)eraWithCalendar:(NSCalendar *)calendar; +- (NSInteger)yearWithCalendar:(NSCalendar *)calendar; +- (NSInteger)monthWithCalendar:(NSCalendar *)calendar; +- (NSInteger)dayWithCalendar:(NSCalendar *)calendar; +- (NSInteger)hourWithCalendar:(NSCalendar *)calendar; +- (NSInteger)minuteWithCalendar:(NSCalendar *)calendar; +- (NSInteger)secondWithCalendar:(NSCalendar *)calendar; +- (NSInteger)weekdayWithCalendar:(NSCalendar *)calendar; +- (NSInteger)weekdayOrdinalWithCalendar:(NSCalendar *)calendar; +- (NSInteger)quarterWithCalendar:(NSCalendar *)calendar; +- (NSInteger)weekOfMonthWithCalendar:(NSCalendar *)calendar; +- (NSInteger)weekOfYearWithCalendar:(NSCalendar *)calendar; +- (NSInteger)yearForWeekOfYearWithCalendar:(NSCalendar *)calendar; + + +#pragma mark - Date Creating ++ (NSDate *)dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day; ++ (NSDate *)dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour minute:(NSInteger)minute second:(NSInteger)second; ++ (NSDate *)dateWithString:(NSString *)dateString formatString:(NSString *)formatString; ++ (NSDate *)dateWithString:(NSString *)dateString formatString:(NSString *)formatString timeZone:(NSTimeZone *)timeZone; + + +#pragma mark - Date Editing +#pragma mark Date By Adding +- (NSDate *)dateByAddingYears:(NSInteger)years; +- (NSDate *)dateByAddingMonths:(NSInteger)months; +- (NSDate *)dateByAddingWeeks:(NSInteger)weeks; +- (NSDate *)dateByAddingDays:(NSInteger)days; +- (NSDate *)dateByAddingHours:(NSInteger)hours; +- (NSDate *)dateByAddingMinutes:(NSInteger)minutes; +- (NSDate *)dateByAddingSeconds:(NSInteger)seconds; +#pragma mark Date By Subtracting +- (NSDate *)dateBySubtractingYears:(NSInteger)years; +- (NSDate *)dateBySubtractingMonths:(NSInteger)months; +- (NSDate *)dateBySubtractingWeeks:(NSInteger)weeks; +- (NSDate *)dateBySubtractingDays:(NSInteger)days; +- (NSDate *)dateBySubtractingHours:(NSInteger)hours; +- (NSDate *)dateBySubtractingMinutes:(NSInteger)minutes; +- (NSDate *)dateBySubtractingSeconds:(NSInteger)seconds; + +#pragma mark - Date Comparison +#pragma mark Time From +-(NSInteger)yearsFrom:(NSDate *)date; +-(NSInteger)monthsFrom:(NSDate *)date; +-(NSInteger)weeksFrom:(NSDate *)date; +-(NSInteger)daysFrom:(NSDate *)date; +-(double)hoursFrom:(NSDate *)date; +-(double)minutesFrom:(NSDate *)date; +-(double)secondsFrom:(NSDate *)date; +#pragma mark Time From With Calendar +-(NSInteger)yearsFrom:(NSDate *)date calendar:(NSCalendar *)calendar; +-(NSInteger)monthsFrom:(NSDate *)date calendar:(NSCalendar *)calendar; +-(NSInteger)weeksFrom:(NSDate *)date calendar:(NSCalendar *)calendar; +-(NSInteger)daysFrom:(NSDate *)date calendar:(NSCalendar *)calendar; + +#pragma mark Time Until +-(NSInteger)yearsUntil; +-(NSInteger)monthsUntil; +-(NSInteger)weeksUntil; +-(NSInteger)daysUntil; +-(double)hoursUntil; +-(double)minutesUntil; +-(double)secondsUntil; +#pragma mark Time Ago +-(NSInteger)yearsAgo; +-(NSInteger)monthsAgo; +-(NSInteger)weeksAgo; +-(NSInteger)daysAgo; +-(double)hoursAgo; +-(double)minutesAgo; +-(double)secondsAgo; +#pragma mark Earlier Than +-(NSInteger)yearsEarlierThan:(NSDate *)date; +-(NSInteger)monthsEarlierThan:(NSDate *)date; +-(NSInteger)weeksEarlierThan:(NSDate *)date; +-(NSInteger)daysEarlierThan:(NSDate *)date; +-(double)hoursEarlierThan:(NSDate *)date; +-(double)minutesEarlierThan:(NSDate *)date; +-(double)secondsEarlierThan:(NSDate *)date; +#pragma mark Later Than +-(NSInteger)yearsLaterThan:(NSDate *)date; +-(NSInteger)monthsLaterThan:(NSDate *)date; +-(NSInteger)weeksLaterThan:(NSDate *)date; +-(NSInteger)daysLaterThan:(NSDate *)date; +-(double)hoursLaterThan:(NSDate *)date; +-(double)minutesLaterThan:(NSDate *)date; +-(double)secondsLaterThan:(NSDate *)date; +#pragma mark Comparators +-(BOOL)isEarlierThan:(NSDate *)date; +-(BOOL)isLaterThan:(NSDate *)date; +-(BOOL)isEarlierThanOrEqualTo:(NSDate *)date; +-(BOOL)isLaterThanOrEqualTo:(NSDate *)date; + +#pragma mark - Formatted Dates +#pragma mark Formatted With Style +-(NSString *)formattedDateWithStyle:(NSDateFormatterStyle)style; +-(NSString *)formattedDateWithStyle:(NSDateFormatterStyle)style timeZone:(NSTimeZone *)timeZone; +-(NSString *)formattedDateWithStyle:(NSDateFormatterStyle)style locale:(NSLocale *)locale; +-(NSString *)formattedDateWithStyle:(NSDateFormatterStyle)style timeZone:(NSTimeZone *)timeZone locale:(NSLocale *)locale; +#pragma mark Formatted With Format +-(NSString *)formattedDateWithFormat:(NSString *)format; +-(NSString *)formattedDateWithFormat:(NSString *)format timeZone:(NSTimeZone *)timeZone; +-(NSString *)formattedDateWithFormat:(NSString *)format locale:(NSLocale *)locale; +-(NSString *)formattedDateWithFormat:(NSString *)format timeZone:(NSTimeZone *)timeZone locale:(NSLocale *)locale; + +#pragma mark - Helpers ++(NSString *)defaultCalendarIdentifier; ++ (void)setDefaultCalendarIdentifier:(NSString *)identifier; +@end diff --git a/iosApp/iosApp/Libs/DateTools/NSDate+DateTools.m b/iosApp/iosApp/Libs/DateTools/NSDate+DateTools.m new file mode 100644 index 0000000..63d16a8 --- /dev/null +++ b/iosApp/iosApp/Libs/DateTools/NSDate+DateTools.m @@ -0,0 +1,1743 @@ +// 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 "NSDate+DateTools.h" + +typedef NS_ENUM(NSUInteger, DTDateComponent){ + DTDateComponentEra, + DTDateComponentYear, + DTDateComponentMonth, + DTDateComponentDay, + DTDateComponentHour, + DTDateComponentMinute, + DTDateComponentSecond, + DTDateComponentWeekday, + DTDateComponentWeekdayOrdinal, + DTDateComponentQuarter, + DTDateComponentWeekOfMonth, + DTDateComponentWeekOfYear, + DTDateComponentYearForWeekOfYear, + DTDateComponentDayOfYear +}; + +typedef NS_ENUM(NSUInteger, DateAgoFormat){ + DateAgoLong, + DateAgoLongUsingNumericDatesAndTimes, + DateAgoLongUsingNumericDates, + DateAgoLongUsingNumericTimes, + DateAgoShort, + DateAgoWeek, +}; + +typedef NS_ENUM(NSUInteger, DateAgoValues){ + YearsAgo, + MonthsAgo, + WeeksAgo, + DaysAgo, + HoursAgo, + MinutesAgo, + SecondsAgo +}; + +static const unsigned int allCalendarUnitFlags = NSCalendarUnitYear | NSCalendarUnitQuarter | NSCalendarUnitMonth | NSCalendarUnitWeekOfYear | NSCalendarUnitWeekOfMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitEra | NSCalendarUnitWeekday | NSCalendarUnitWeekdayOrdinal | NSCalendarUnitWeekOfYear; + +static NSString *defaultCalendarIdentifier = nil; +static NSCalendar *implicitCalendar = nil; + +@implementation NSDate (DateTools) + ++ (void)load { + [self setDefaultCalendarIdentifier:NSCalendarIdentifierGregorian]; +} + +#pragma mark - Time Ago + + +/** + * Takes in a date and returns a string with the most convenient unit of time representing + * how far in the past that date is from now. + * + * @param date - Date to be measured from now + * + * @return NSString - Formatted return string + */ ++ (NSString*)timeAgoSinceDate:(NSDate*)date{ + return [date timeAgoSinceDate:[NSDate date]]; +} + +/** + * Takes in a date and returns a shortened string with the most convenient unit of time representing + * how far in the past that date is from now. + * + * @param date - Date to be measured from now + * + * @return NSString - Formatted return string + */ ++ (NSString*)shortTimeAgoSinceDate:(NSDate*)date{ + return [date shortTimeAgoSinceDate:[NSDate date]]; +} + ++ (NSString*)weekTimeAgoSinceDate:(NSDate*)date{ + return [date weekTimeAgoSinceDate:[NSDate date]]; +} + +/** + * Returns a string with the most convenient unit of time representing + * how far in the past that date is from now. + * + * @return NSString - Formatted return string + */ +- (NSString*)timeAgoSinceNow{ + return [self timeAgoSinceDate:[NSDate date]]; +} + +/** + * Returns a shortened string with the most convenient unit of time representing + * how far in the past that date is from now. + * + * @return NSString - Formatted return string + */ +- (NSString *)shortTimeAgoSinceNow{ + return [self shortTimeAgoSinceDate:[NSDate date]]; +} + +- (NSString *)weekTimeAgoSinceNow{ + return [self weekTimeAgoSinceDate:[NSDate date]]; +} + +- (NSString *)timeAgoSinceDate:(NSDate *)date{ + return [self timeAgoSinceDate:date numericDates:NO]; +} + +- (NSString *)timeAgoSinceDate:(NSDate *)date numericDates:(BOOL)useNumericDates{ + return [self timeAgoSinceDate:date numericDates:useNumericDates numericTimes:NO]; +} + +- (NSString *)timeAgoSinceDate:(NSDate *)date numericDates:(BOOL)useNumericDates numericTimes:(BOOL)useNumericTimes{ + if (useNumericDates && useNumericTimes) { + return [self timeAgoSinceDate:date format:DateAgoLongUsingNumericDatesAndTimes]; + } else if (useNumericDates) { + return [self timeAgoSinceDate:date format:DateAgoLongUsingNumericDates]; + } else if (useNumericTimes) { + return [self timeAgoSinceDate:date format:DateAgoLongUsingNumericDates]; + } else { + return [self timeAgoSinceDate:date format:DateAgoLong]; + } +} + +- (NSString *)shortTimeAgoSinceDate:(NSDate *)date{ + return [self timeAgoSinceDate:date format:DateAgoShort]; +} + +- (NSString *)weekTimeAgoSinceDate:(NSDate *)date{ + return [self timeAgoSinceDate:date format:DateAgoWeek]; +} + +- (NSString *)timeAgoSinceDate:(NSDate *)date format:(DateAgoFormat)format { + + NSCalendar *calendar = [NSCalendar currentCalendar]; + NSDate *earliest = [self earlierDate:date]; + NSDate *latest = (earliest == self) ? date : self; + + // if timeAgo < 24h => compare DateTime else compare Date only + NSUInteger upToHours = NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour; + NSDateComponents *difference = [calendar components:upToHours fromDate:earliest toDate:latest options:0]; + + if (difference.hour < 24) { + if (difference.hour >= 1) { + return [self localizedStringFor:format valueType:HoursAgo value:difference.hour]; + } else if (difference.minute >= 1) { + return [self localizedStringFor:format valueType:MinutesAgo value:difference.minute]; + } else { + return [self localizedStringFor:format valueType:SecondsAgo value:difference.second]; + } + + } else { + NSUInteger bigUnits = NSCalendarUnitTimeZone | NSCalendarUnitDay | NSCalendarUnitWeekOfYear | NSCalendarUnitMonth | NSCalendarUnitYear; + + NSDateComponents *components = [calendar components:bigUnits fromDate:earliest]; + earliest = [calendar dateFromComponents:components]; + + components = [calendar components:bigUnits fromDate:latest]; + latest = [calendar dateFromComponents:components]; + + difference = [calendar components:bigUnits fromDate:earliest toDate:latest options:0]; + + if (difference.year >= 1) { + return [self localizedStringFor:format valueType:YearsAgo value:difference.year]; + } else if (difference.month >= 1) { + return [self localizedStringFor:format valueType:MonthsAgo value:difference.month]; + } else if (difference.weekOfYear >= 1) { + return [self localizedStringFor:format valueType:WeeksAgo value:difference.weekOfYear]; + } else { + return [self localizedStringFor:format valueType:DaysAgo value:difference.day]; + } + } +} + +- (NSString *)localizedStringFor:(DateAgoFormat)format valueType:(DateAgoValues)valueType value:(NSInteger)value { + BOOL isShort = format == DateAgoShort; + BOOL isNumericDate = format == DateAgoLongUsingNumericDates || format == DateAgoLongUsingNumericDatesAndTimes; + BOOL isNumericTime = format == DateAgoLongUsingNumericTimes || format == DateAgoLongUsingNumericDatesAndTimes; + BOOL isWeek = format == DateAgoWeek; + + switch (valueType) { + case YearsAgo: + if (isShort) { + return [self logicLocalizedStringFromFormat:@"%%d%@y" withValue:value]; + } else if (value >= 2) { + return [self logicLocalizedStringFromFormat:@"%%d %@years ago" withValue:value]; + } else if (isNumericDate) { + return DateToolsLocalizedStrings(@"1 year ago"); + } else { + return DateToolsLocalizedStrings(@"Last year"); + } + case MonthsAgo: + if (isShort) { + return [self logicLocalizedStringFromFormat:@"%%d%@M" withValue:value]; + } else if (value >= 2) { + return [self logicLocalizedStringFromFormat:@"%%d %@months ago" withValue:value]; + } else if (isNumericDate) { + return DateToolsLocalizedStrings(@"1 month ago"); + } else { + return DateToolsLocalizedStrings(@"Last month"); + } + case WeeksAgo: + if (isShort) { + return [self logicLocalizedStringFromFormat:@"%%d%@w" withValue:value]; + } else if (value >= 2) { + return [self logicLocalizedStringFromFormat:@"%%d %@weeks ago" withValue:value]; + } else if (isNumericDate) { + return DateToolsLocalizedStrings(@"1 week ago"); + } else { + return DateToolsLocalizedStrings(@"Last week"); + } + case DaysAgo: + if (isShort) { + return [self logicLocalizedStringFromFormat:@"%%d%@d" withValue:value]; + } else if (value >= 2) { + if (isWeek && value <= 7) { + NSDateFormatter *dayDateFormatter = [[NSDateFormatter alloc]init]; + dayDateFormatter.dateFormat = @"EEE"; + NSString *eee = [dayDateFormatter stringFromDate:self]; + + return DateToolsLocalizedStrings(eee); + } + + return [self logicLocalizedStringFromFormat:@"%%d %@days ago" withValue:value]; + } else if (isNumericDate) { + return DateToolsLocalizedStrings(@"1 day ago"); + } else { + return DateToolsLocalizedStrings(@"Yesterday"); + } + case HoursAgo: + if (isShort) { + return [self logicLocalizedStringFromFormat:@"%%d%@h" withValue:value]; + } else if (value >= 2) { + return [self logicLocalizedStringFromFormat:@"%%d %@hours ago" withValue:value]; + } else if (isNumericTime) { + return DateToolsLocalizedStrings(@"1 hour ago"); + } else { + return DateToolsLocalizedStrings(@"An hour ago"); + } + case MinutesAgo: + if (isShort) { + return [self logicLocalizedStringFromFormat:@"%%d%@m" withValue:value]; + } else if (value >= 2) { + return [self logicLocalizedStringFromFormat:@"%%d %@minutes ago" withValue:value]; + } else if (isNumericTime) { + return DateToolsLocalizedStrings(@"1 minute ago"); + } else { + return DateToolsLocalizedStrings(@"A minute ago"); + } + case SecondsAgo: + if (isShort) { + return [self logicLocalizedStringFromFormat:@"%%d%@s" withValue:value]; + } else if (value >= 2) { + return [self logicLocalizedStringFromFormat:@"%%d %@seconds ago" withValue:value]; + } else if (isNumericTime) { + return DateToolsLocalizedStrings(@"1 second ago"); + } else { + return DateToolsLocalizedStrings(@"Just now"); + } + } + return nil; +} + +- (NSString *) logicLocalizedStringFromFormat:(NSString *)format withValue:(NSInteger)value{ + NSString * localeFormat = [NSString stringWithFormat:format, [self getLocaleFormatUnderscoresWithValue:value]]; + return [NSString stringWithFormat:DateToolsLocalizedStrings(localeFormat), value]; +} + +- (NSString *)getLocaleFormatUnderscoresWithValue:(double)value{ + NSString *localeCode = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]; + + // Russian (ru) and Ukrainian (uk) + if([localeCode isEqualToString:@"ru-RU"] || [localeCode isEqualToString:@"uk"]) { + int XY = (int)floor(value) % 100; + int Y = (int)floor(value) % 10; + + if(Y == 0 || Y > 4 || (XY > 10 && XY < 15)) { + return @""; + } + + if(Y > 1 && Y < 5 && (XY < 10 || XY > 20)) { + return @"_"; + } + + if(Y == 1 && XY != 11) { + return @"__"; + } + } + + // Add more languages here, which are have specific translation rules... + + return @""; +} + +#pragma mark - Date Components Without Calendar +/** + * Returns the era of the receiver. (0 for BC, 1 for AD for Gregorian) + * + * @return NSInteger + */ +- (NSInteger)era{ + return [self componentForDate:self type:DTDateComponentEra calendar:nil]; +} + +/** + * Returns the year of the receiver. + * + * @return NSInteger + */ +- (NSInteger)year{ + return [self componentForDate:self type:DTDateComponentYear calendar:nil]; +} + +/** + * Returns the month of the year of the receiver. + * + * @return NSInteger + */ +- (NSInteger)month{ + return [self componentForDate:self type:DTDateComponentMonth calendar:nil]; +} + +/** + * Returns the day of the month of the receiver. + * + * @return NSInteger + */ +- (NSInteger)day{ + return [self componentForDate:self type:DTDateComponentDay calendar:nil]; +} + +/** + * Returns the hour of the day of the receiver. (0-24) + * + * @return NSInteger + */ +- (NSInteger)hour{ + return [self componentForDate:self type:DTDateComponentHour calendar:nil]; +} + +/** + * Returns the minute of the receiver. (0-59) + * + * @return NSInteger + */ +- (NSInteger)minute{ + return [self componentForDate:self type:DTDateComponentMinute calendar:nil]; +} + +/** + * Returns the second of the receiver. (0-59) + * + * @return NSInteger + */ +- (NSInteger)second{ + return [self componentForDate:self type:DTDateComponentSecond calendar:nil]; +} + +/** + * Returns the day of the week of the receiver. + * + * @return NSInteger + */ +- (NSInteger)weekday{ + return [self componentForDate:self type:DTDateComponentWeekday calendar:nil]; +} + +/** + * Returns the ordinal for the day of the week of the receiver. + * + * @return NSInteger + */ +- (NSInteger)weekdayOrdinal{ + return [self componentForDate:self type:DTDateComponentWeekdayOrdinal calendar:nil]; +} + +/** + * Returns the quarter of the receiver. + * + * @return NSInteger + */ +- (NSInteger)quarter{ + return [self componentForDate:self type:DTDateComponentQuarter calendar:nil]; +} + +/** + * Returns the week of the month of the receiver. + * + * @return NSInteger + */ +- (NSInteger)weekOfMonth{ + return [self componentForDate:self type:DTDateComponentWeekOfMonth calendar:nil]; +} + +/** + * Returns the week of the year of the receiver. + * + * @return NSInteger + */ +- (NSInteger)weekOfYear{ + return [self componentForDate:self type:DTDateComponentWeekOfYear calendar:nil]; +} + +/** + * I honestly don't know much about this value... + * + * @return NSInteger + */ +- (NSInteger)yearForWeekOfYear{ + return [self componentForDate:self type:DTDateComponentYearForWeekOfYear calendar:nil]; +} + +/** + * Returns how many days are in the month of the receiver. + * + * @return NSInteger + */ +- (NSInteger)daysInMonth{ + NSCalendar *calendar = [NSCalendar currentCalendar]; + NSRange days = [calendar rangeOfUnit:NSCalendarUnitDay + inUnit:NSCalendarUnitMonth + forDate:self]; + return days.length; +} + +/** + * Returns the day of the year of the receiver. (1-365 or 1-366 for leap year) + * + * @return NSInteger + */ +- (NSInteger)dayOfYear{ + return [self componentForDate:self type:DTDateComponentDayOfYear calendar:nil]; +} + +/** + * Returns how many days are in the year of the receiver. + * + * @return NSInteger + */ +-(NSInteger)daysInYear{ + if (self.isInLeapYear) { + return 366; + } + + return 365; +} + +/** + * Returns whether the receiver falls in a leap year. + * + * @return NSInteger + */ +-(BOOL)isInLeapYear{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *dateComponents = [calendar components:allCalendarUnitFlags fromDate:self]; + + if (dateComponents.year%400 == 0){ + return YES; + } + else if (dateComponents.year%100 == 0){ + return NO; + } + else if (dateComponents.year%4 == 0){ + return YES; + } + + return NO; +} + +- (BOOL)isToday { + NSCalendar *cal = [NSCalendar currentCalendar]; + NSDateComponents *components = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:[NSDate date]]; + NSDate *today = [cal dateFromComponents:components]; + components = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:self]; + NSDate *otherDate = [cal dateFromComponents:components]; + + return [today isEqualToDate:otherDate]; +} + +- (BOOL)isTomorrow { + NSCalendar *cal = [NSCalendar currentCalendar]; + NSDateComponents *components = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:[[NSDate date] dateByAddingDays:1]]; + NSDate *tomorrow = [cal dateFromComponents:components]; + components = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:self]; + NSDate *otherDate = [cal dateFromComponents:components]; + + return [tomorrow isEqualToDate:otherDate]; +} + +-(BOOL)isYesterday{ + NSCalendar *cal = [NSCalendar currentCalendar]; + NSDateComponents *components = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:[[NSDate date] dateBySubtractingDays:1]]; + NSDate *tomorrow = [cal dateFromComponents:components]; + components = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:self]; + NSDate *otherDate = [cal dateFromComponents:components]; + + return [tomorrow isEqualToDate:otherDate]; +} + +- (BOOL)isWeekend { + NSCalendar *calendar = [NSCalendar currentCalendar]; + NSRange weekdayRange = [calendar maximumRangeOfUnit:NSCalendarUnitWeekday]; + NSDateComponents *components = [calendar components:NSCalendarUnitWeekday + fromDate:self]; + NSUInteger weekdayOfSomeDate = [components weekday]; + + BOOL result = NO; + + if (weekdayOfSomeDate == weekdayRange.location || weekdayOfSomeDate == weekdayRange.length) + result = YES; + + return result; +} + + +/** + * Returns whether two dates fall on the same day. + * + * @param date NSDate - Date to compare with sender + * @return BOOL - YES if both paramter dates fall on the same day, NO otherwise + */ +-(BOOL)isSameDay:(NSDate *)date { + return [NSDate isSameDay:self asDate:date]; +} + +/** + * Returns whether two dates fall on the same day. + * + * @param date NSDate - First date to compare + * @param compareDate NSDate - Second date to compare + * @return BOOL - YES if both paramter dates fall on the same day, NO otherwise + */ ++ (BOOL)isSameDay:(NSDate *)date asDate:(NSDate *)compareDate +{ + NSCalendar *cal = [NSCalendar currentCalendar]; + + NSDateComponents *components = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:date]; + NSDate *dateOne = [cal dateFromComponents:components]; + + components = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:compareDate]; + NSDate *dateTwo = [cal dateFromComponents:components]; + + return [dateOne isEqualToDate:dateTwo]; +} + +#pragma mark - Date Components With Calendar +/** + * Returns the era of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the era (0 for BC, 1 for AD for Gregorian) + */ +- (NSInteger)eraWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentEra calendar:calendar]; +} + +/** + * Returns the year of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the year as an integer + */ +- (NSInteger)yearWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentYear calendar:calendar]; +} + +/** + * Returns the month of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the month as an integer + */ +- (NSInteger)monthWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentMonth calendar:calendar]; +} + +/** + * Returns the day of the month of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the day of the month as an integer + */ +- (NSInteger)dayWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentDay calendar:calendar]; +} + +/** + * Returns the hour of the day of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the hour of the day as an integer + */ +- (NSInteger)hourWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentHour calendar:calendar]; +} + +/** + * Returns the minute of the hour of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the minute of the hour as an integer + */ +- (NSInteger)minuteWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentMinute calendar:calendar]; +} + +/** + * Returns the second of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the second as an integer + */ +- (NSInteger)secondWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentSecond calendar:calendar]; +} + +/** + * Returns the weekday of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the weekday as an integer + */ +- (NSInteger)weekdayWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentWeekday calendar:calendar]; +} + +/** + * Returns the weekday ordinal of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the weekday ordinal as an integer + */ +- (NSInteger)weekdayOrdinalWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentWeekdayOrdinal calendar:calendar]; +} + +/** + * Returns the quarter of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the quarter as an integer + */ +- (NSInteger)quarterWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentQuarter calendar:calendar]; +} + +/** + * Returns the week of the month of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the week of the month as an integer + */ +- (NSInteger)weekOfMonthWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentWeekOfMonth calendar:calendar]; +} + +/** + * Returns the week of the year of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the week of the year as an integer + */ +- (NSInteger)weekOfYearWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentWeekOfYear calendar:calendar]; +} + +/** + * Returns the year for week of the year (???) of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the year for week of the year as an integer + */ +- (NSInteger)yearForWeekOfYearWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentYearForWeekOfYear calendar:calendar]; +} + + +/** + * Returns the day of the year of the receiver from a given calendar + * + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - represents the day of the year as an integer + */ +- (NSInteger)dayOfYearWithCalendar:(NSCalendar *)calendar{ + return [self componentForDate:self type:DTDateComponentDayOfYear calendar:calendar]; +} + +/** + * Takes in a date, calendar and desired date component and returns the desired NSInteger + * representation for that component + * + * @param date NSDate - The date to be be mined for a desired component + * @param component DTDateComponent - The desired component (i.e. year, day, week, etc) + * @param calendar NSCalendar - The calendar to be used in the processing (Defaults to Gregorian) + * + * @return NSInteger + */ +-(NSInteger)componentForDate:(NSDate *)date type:(DTDateComponent)component calendar:(NSCalendar *)calendar{ + if (!calendar) { + calendar = [[self class] implicitCalendar]; + } + + unsigned int unitFlags = 0; + + if (component == DTDateComponentYearForWeekOfYear) { + unitFlags = NSCalendarUnitYear | NSCalendarUnitQuarter | NSCalendarUnitMonth | NSCalendarUnitWeekOfYear | NSCalendarUnitWeekOfMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitEra | NSCalendarUnitWeekday | NSCalendarUnitWeekdayOrdinal | NSCalendarUnitWeekOfYear | NSCalendarUnitYearForWeekOfYear; + } + else { + unitFlags = allCalendarUnitFlags; + } + + NSDateComponents *dateComponents = [calendar components:unitFlags fromDate:date]; + + switch (component) { + case DTDateComponentEra: + return [dateComponents era]; + case DTDateComponentYear: + return [dateComponents year]; + case DTDateComponentMonth: + return [dateComponents month]; + case DTDateComponentDay: + return [dateComponents day]; + case DTDateComponentHour: + return [dateComponents hour]; + case DTDateComponentMinute: + return [dateComponents minute]; + case DTDateComponentSecond: + return [dateComponents second]; + case DTDateComponentWeekday: + return [dateComponents weekday]; + case DTDateComponentWeekdayOrdinal: + return [dateComponents weekdayOrdinal]; + case DTDateComponentQuarter: + return [dateComponents quarter]; + case DTDateComponentWeekOfMonth: + return [dateComponents weekOfMonth]; + case DTDateComponentWeekOfYear: + return [dateComponents weekOfYear]; + case DTDateComponentYearForWeekOfYear: + return [dateComponents yearForWeekOfYear]; + case DTDateComponentDayOfYear: + return [calendar ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitYear forDate:date]; + default: + break; + } + + return 0; +} + +#pragma mark - Date Creating ++ (NSDate *)dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day { + + return [self dateWithYear:year month:month day:day hour:0 minute:0 second:0]; +} + ++ (NSDate *)dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour minute:(NSInteger)minute second:(NSInteger)second { + + NSDate *nsDate = nil; + NSDateComponents *components = [[NSDateComponents alloc] init]; + + components.year = year; + components.month = month; + components.day = day; + components.hour = hour; + components.minute = minute; + components.second = second; + + nsDate = [[[self class] implicitCalendar] dateFromComponents:components]; + + return nsDate; +} + ++ (NSDate *)dateWithString:(NSString *)dateString formatString:(NSString *)formatString { + + return [self dateWithString:dateString formatString:formatString timeZone:[NSTimeZone systemTimeZone]]; +} + ++ (NSDate *)dateWithString:(NSString *)dateString formatString:(NSString *)formatString timeZone:(NSTimeZone *)timeZone { + + static NSDateFormatter *parser = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + parser = [[NSDateFormatter alloc] init]; + }); + + parser.dateStyle = NSDateFormatterNoStyle; + parser.timeStyle = NSDateFormatterNoStyle; + parser.timeZone = timeZone; + parser.dateFormat = formatString; + + return [parser dateFromString:dateString]; +} + + +#pragma mark - Date Editing +#pragma mark Date By Adding +/** + * Returns a date representing the receivers date shifted later by the provided number of years. + * + * @param years NSInteger - Number of years to add + * + * @return NSDate - Date modified by the number of desired years + */ +- (NSDate *)dateByAddingYears:(NSInteger)years{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setYear:years]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted later by the provided number of months. + * + * @param months NSInteger - Number of months to add + * + * @return NSDate - Date modified by the number of desired months + */ +- (NSDate *)dateByAddingMonths:(NSInteger)months{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setMonth:months]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted later by the provided number of weeks. + * + * @param weeks NSInteger - Number of weeks to add + * + * @return NSDate - Date modified by the number of desired weeks + */ +- (NSDate *)dateByAddingWeeks:(NSInteger)weeks{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setWeekOfYear:weeks]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted later by the provided number of days. + * + * @param days NSInteger - Number of days to add + * + * @return NSDate - Date modified by the number of desired days + */ +- (NSDate *)dateByAddingDays:(NSInteger)days{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setDay:days]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted later by the provided number of hours. + * + * @param hours NSInteger - Number of hours to add + * + * @return NSDate - Date modified by the number of desired hours + */ +- (NSDate *)dateByAddingHours:(NSInteger)hours{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setHour:hours]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted later by the provided number of minutes. + * + * @param minutes NSInteger - Number of minutes to add + * + * @return NSDate - Date modified by the number of desired minutes + */ +- (NSDate *)dateByAddingMinutes:(NSInteger)minutes{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setMinute:minutes]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted later by the provided number of seconds. + * + * @param seconds NSInteger - Number of seconds to add + * + * @return NSDate - Date modified by the number of desired seconds + */ +- (NSDate *)dateByAddingSeconds:(NSInteger)seconds{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setSecond:seconds]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +#pragma mark Date By Subtracting +/** + * Returns a date representing the receivers date shifted earlier by the provided number of years. + * + * @param years NSInteger - Number of years to subtract + * + * @return NSDate - Date modified by the number of desired years + */ +- (NSDate *)dateBySubtractingYears:(NSInteger)years{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setYear:-1*years]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted earlier by the provided number of months. + * + * @param months NSInteger - Number of months to subtract + * + * @return NSDate - Date modified by the number of desired months + */ +- (NSDate *)dateBySubtractingMonths:(NSInteger)months{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setMonth:-1*months]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted earlier by the provided number of weeks. + * + * @param weeks NSInteger - Number of weeks to subtract + * + * @return NSDate - Date modified by the number of desired weeks + */ +- (NSDate *)dateBySubtractingWeeks:(NSInteger)weeks{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setWeekOfYear:-1*weeks]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted earlier by the provided number of days. + * + * @param days NSInteger - Number of days to subtract + * + * @return NSDate - Date modified by the number of desired days + */ +- (NSDate *)dateBySubtractingDays:(NSInteger)days{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setDay:-1*days]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted earlier by the provided number of hours. + * + * @param hours NSInteger - Number of hours to subtract + * + * @return NSDate - Date modified by the number of desired hours + */ +- (NSDate *)dateBySubtractingHours:(NSInteger)hours{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setHour:-1*hours]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted earlier by the provided number of minutes. + * + * @param minutes NSInteger - Number of minutes to subtract + * + * @return NSDate - Date modified by the number of desired minutes + */ +- (NSDate *)dateBySubtractingMinutes:(NSInteger)minutes{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setMinute:-1*minutes]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +/** + * Returns a date representing the receivers date shifted earlier by the provided number of seconds. + * + * @param seconds NSInteger - Number of seconds to subtract + * + * @return NSDate - Date modified by the number of desired seconds + */ +- (NSDate *)dateBySubtractingSeconds:(NSInteger)seconds{ + NSCalendar *calendar = [[self class] implicitCalendar]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setSecond:-1*seconds]; + + return [calendar dateByAddingComponents:components toDate:self options:0]; +} + +#pragma mark - Date Comparison +#pragma mark Time From +/** + * Returns an NSInteger representing the amount of time in years between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * Uses the default Gregorian calendar + * + * @param date NSDate - The provided date for comparison + * + * @return NSInteger - The NSInteger representation of the years between receiver and provided date + */ +-(NSInteger)yearsFrom:(NSDate *)date{ + return [self yearsFrom:date calendar:nil]; +} + +/** + * Returns an NSInteger representing the amount of time in months between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * Uses the default Gregorian calendar + * + * @param date NSDate - The provided date for comparison + * + * @return NSInteger - The NSInteger representation of the years between receiver and provided date + */ +-(NSInteger)monthsFrom:(NSDate *)date{ + return [self monthsFrom:date calendar:nil]; +} + +/** + * Returns an NSInteger representing the amount of time in weeks between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * Uses the default Gregorian calendar + * + * @param date NSDate - The provided date for comparison + * + * @return NSInteger - The double representation of the weeks between receiver and provided date + */ +-(NSInteger)weeksFrom:(NSDate *)date{ + return [self weeksFrom:date calendar:nil]; +} + +/** + * Returns an NSInteger representing the amount of time in days between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * Uses the default Gregorian calendar + * + * @param date NSDate - The provided date for comparison + * + * @return NSInteger - The double representation of the days between receiver and provided date + */ +-(NSInteger)daysFrom:(NSDate *)date{ + return [self daysFrom:date calendar:nil]; +} + +/** + * Returns an NSInteger representing the amount of time in hours between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * + * @param date NSDate - The provided date for comparison + * + * @return double - The double representation of the hours between receiver and provided date + */ +-(double)hoursFrom:(NSDate *)date{ + return ([self timeIntervalSinceDate:date])/SECONDS_IN_HOUR; +} + +/** + * Returns an NSInteger representing the amount of time in minutes between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * + * @param date NSDate - The provided date for comparison + * + * @return double - The double representation of the minutes between receiver and provided date + */ +-(double)minutesFrom:(NSDate *)date{ + return ([self timeIntervalSinceDate:date])/SECONDS_IN_MINUTE; +} + +/** + * Returns an NSInteger representing the amount of time in seconds between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * + * @param date NSDate - The provided date for comparison + * + * @return double - The double representation of the seconds between receiver and provided date + */ +-(double)secondsFrom:(NSDate *)date{ + return [self timeIntervalSinceDate:date]; +} + +#pragma mark Time From With Calendar +/** + * Returns an NSInteger representing the amount of time in years between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * + * @param date NSDate - The provided date for comparison + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - The double representation of the years between receiver and provided date + */ +-(NSInteger)yearsFrom:(NSDate *)date calendar:(NSCalendar *)calendar{ + if (!calendar) { + calendar = [[self class] implicitCalendar]; + } + + NSDate *earliest = [self earlierDate:date]; + NSDate *latest = (earliest == self) ? date : self; + NSInteger multiplier = (earliest == self) ? -1 : 1; + NSDateComponents *components = [calendar components:NSCalendarUnitYear fromDate:earliest toDate:latest options:0]; + return multiplier*components.year; +} + +/** + * Returns an NSInteger representing the amount of time in months between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * + * @param date NSDate - The provided date for comparison + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - The double representation of the months between receiver and provided date + */ +-(NSInteger)monthsFrom:(NSDate *)date calendar:(NSCalendar *)calendar{ + if (!calendar) { + calendar = [[self class] implicitCalendar]; + } + + NSDate *earliest = [self earlierDate:date]; + NSDate *latest = (earliest == self) ? date : self; + NSInteger multiplier = (earliest == self) ? -1 : 1; + NSDateComponents *components = [calendar components:allCalendarUnitFlags fromDate:earliest toDate:latest options:0]; + return multiplier*(components.month + 12*components.year); +} + +/** + * Returns an NSInteger representing the amount of time in weeks between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * + * @param date NSDate - The provided date for comparison + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - The double representation of the weeks between receiver and provided date + */ +-(NSInteger)weeksFrom:(NSDate *)date calendar:(NSCalendar *)calendar{ + if (!calendar) { + calendar = [[self class] implicitCalendar]; + } + + NSDate *earliest = [self earlierDate:date]; + NSDate *latest = (earliest == self) ? date : self; + NSInteger multiplier = (earliest == self) ? -1 : 1; + NSDateComponents *components = [calendar components:NSCalendarUnitWeekOfYear fromDate:earliest toDate:latest options:0]; + return multiplier*components.weekOfYear; +} + +/** + * Returns an NSInteger representing the amount of time in days between the receiver and the provided date. + * If the receiver is earlier than the provided date, the returned value will be negative. + * + * @param date NSDate - The provided date for comparison + * @param calendar NSCalendar - The calendar to be used in the calculation + * + * @return NSInteger - The double representation of the days between receiver and provided date + */ +-(NSInteger)daysFrom:(NSDate *)date calendar:(NSCalendar *)calendar{ + if (!calendar) { + calendar = [[self class] implicitCalendar]; + } + + NSDate *earliest = [self earlierDate:date]; + NSDate *latest = (earliest == self) ? date : self; + NSInteger multiplier = (earliest == self) ? -1 : 1; + NSDateComponents *components = [calendar components:NSCalendarUnitDay fromDate:earliest toDate:latest options:0]; + return multiplier*components.day; +} + +#pragma mark Time Until +/** + * Returns the number of years until the receiver's date. Returns 0 if the receiver is the same or earlier than now. + * + * @return NSInteger representiation of years + */ +-(NSInteger)yearsUntil{ + return [self yearsLaterThan:[NSDate date]]; +} + +/** + * Returns the number of months until the receiver's date. Returns 0 if the receiver is the same or earlier than now. + * + * @return NSInteger representiation of months + */ +-(NSInteger)monthsUntil{ + return [self monthsLaterThan:[NSDate date]]; +} + +/** + * Returns the number of weeks until the receiver's date. Returns 0 if the receiver is the same or earlier than now. + * + * @return NSInteger representiation of weeks + */ +-(NSInteger)weeksUntil{ + return [self weeksLaterThan:[NSDate date]]; +} + +/** + * Returns the number of days until the receiver's date. Returns 0 if the receiver is the same or earlier than now. + * + * @return NSInteger representiation of days + */ +-(NSInteger)daysUntil{ + return [self daysLaterThan:[NSDate date]]; +} + +/** + * Returns the number of hours until the receiver's date. Returns 0 if the receiver is the same or earlier than now. + * + * @return double representiation of hours + */ +-(double)hoursUntil{ + return [self hoursLaterThan:[NSDate date]]; +} + +/** + * Returns the number of minutes until the receiver's date. Returns 0 if the receiver is the same or earlier than now. + * + * @return double representiation of minutes + */ +-(double)minutesUntil{ + return [self minutesLaterThan:[NSDate date]]; +} + +/** + * Returns the number of seconds until the receiver's date. Returns 0 if the receiver is the same or earlier than now. + * + * @return double representiation of seconds + */ +-(double)secondsUntil{ + return [self secondsLaterThan:[NSDate date]]; +} + +#pragma mark Time Ago +/** + * Returns the number of years the receiver's date is earlier than now. Returns 0 if the receiver is the same or later than now. + * + * @return NSInteger representiation of years + */ +-(NSInteger)yearsAgo{ + return [self yearsEarlierThan:[NSDate date]]; +} + +/** + * Returns the number of months the receiver's date is earlier than now. Returns 0 if the receiver is the same or later than now. + * + * @return NSInteger representiation of months + */ +-(NSInteger)monthsAgo{ + return [self monthsEarlierThan:[NSDate date]]; +} + +/** + * Returns the number of weeks the receiver's date is earlier than now. Returns 0 if the receiver is the same or later than now. + * + * @return NSInteger representiation of weeks + */ +-(NSInteger)weeksAgo{ + return [self weeksEarlierThan:[NSDate date]]; +} + +/** + * Returns the number of days the receiver's date is earlier than now. Returns 0 if the receiver is the same or later than now. + * + * @return NSInteger representiation of days + */ +-(NSInteger)daysAgo{ + return [self daysEarlierThan:[NSDate date]]; +} + +/** + * Returns the number of hours the receiver's date is earlier than now. Returns 0 if the receiver is the same or later than now. + * + * @return double representiation of hours + */ +-(double)hoursAgo{ + return [self hoursEarlierThan:[NSDate date]]; +} + +/** + * Returns the number of minutes the receiver's date is earlier than now. Returns 0 if the receiver is the same or later than now. + * + * @return double representiation of minutes + */ +-(double)minutesAgo{ + return [self minutesEarlierThan:[NSDate date]]; +} + +/** + * Returns the number of seconds the receiver's date is earlier than now. Returns 0 if the receiver is the same or later than now. + * + * @return double representiation of seconds + */ +-(double)secondsAgo{ + return [self secondsEarlierThan:[NSDate date]]; +} + +#pragma mark Earlier Than +/** + * Returns the number of years the receiver's date is earlier than the provided comparison date. + * Returns 0 if the receiver's date is later than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return NSInteger representing the number of years + */ +-(NSInteger)yearsEarlierThan:(NSDate *)date{ + return ABS(MIN([self yearsFrom:date], 0)); +} + +/** + * Returns the number of months the receiver's date is earlier than the provided comparison date. + * Returns 0 if the receiver's date is later than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return NSInteger representing the number of months + */ +-(NSInteger)monthsEarlierThan:(NSDate *)date{ + return ABS(MIN([self monthsFrom:date], 0)); +} + +/** + * Returns the number of weeks the receiver's date is earlier than the provided comparison date. + * Returns 0 if the receiver's date is later than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return NSInteger representing the number of weeks + */ +-(NSInteger)weeksEarlierThan:(NSDate *)date{ + return ABS(MIN([self weeksFrom:date], 0)); +} + +/** + * Returns the number of days the receiver's date is earlier than the provided comparison date. + * Returns 0 if the receiver's date is later than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return NSInteger representing the number of days + */ +-(NSInteger)daysEarlierThan:(NSDate *)date{ + return ABS(MIN([self daysFrom:date], 0)); +} + +/** + * Returns the number of hours the receiver's date is earlier than the provided comparison date. + * Returns 0 if the receiver's date is later than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return double representing the number of hours + */ +-(double)hoursEarlierThan:(NSDate *)date{ + return ABS(MIN([self hoursFrom:date], 0)); +} + +/** + * Returns the number of minutes the receiver's date is earlier than the provided comparison date. + * Returns 0 if the receiver's date is later than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return double representing the number of minutes + */ +-(double)minutesEarlierThan:(NSDate *)date{ + return ABS(MIN([self minutesFrom:date], 0)); +} + +/** + * Returns the number of seconds the receiver's date is earlier than the provided comparison date. + * Returns 0 if the receiver's date is later than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return double representing the number of seconds + */ +-(double)secondsEarlierThan:(NSDate *)date{ + return ABS(MIN([self secondsFrom:date], 0)); +} + +#pragma mark Later Than +/** + * Returns the number of years the receiver's date is later than the provided comparison date. + * Returns 0 if the receiver's date is earlier than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return NSInteger representing the number of years + */ +-(NSInteger)yearsLaterThan:(NSDate *)date{ + return MAX([self yearsFrom:date], 0); +} + +/** + * Returns the number of months the receiver's date is later than the provided comparison date. + * Returns 0 if the receiver's date is earlier than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return NSInteger representing the number of months + */ +-(NSInteger)monthsLaterThan:(NSDate *)date{ + return MAX([self monthsFrom:date], 0); +} + +/** + * Returns the number of weeks the receiver's date is later than the provided comparison date. + * Returns 0 if the receiver's date is earlier than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return NSInteger representing the number of weeks + */ +-(NSInteger)weeksLaterThan:(NSDate *)date{ + return MAX([self weeksFrom:date], 0); +} + +/** + * Returns the number of days the receiver's date is later than the provided comparison date. + * Returns 0 if the receiver's date is earlier than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return NSInteger representing the number of days + */ +-(NSInteger)daysLaterThan:(NSDate *)date{ + return MAX([self daysFrom:date], 0); +} + +/** + * Returns the number of hours the receiver's date is later than the provided comparison date. + * Returns 0 if the receiver's date is earlier than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return double representing the number of hours + */ +-(double)hoursLaterThan:(NSDate *)date{ + return MAX([self hoursFrom:date], 0); +} + +/** + * Returns the number of minutes the receiver's date is later than the provided comparison date. + * Returns 0 if the receiver's date is earlier than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return double representing the number of minutes + */ +-(double)minutesLaterThan:(NSDate *)date{ + return MAX([self minutesFrom:date], 0); +} + +/** + * Returns the number of seconds the receiver's date is later than the provided comparison date. + * Returns 0 if the receiver's date is earlier than or equal to the provided comparison date. + * + * @param date NSDate - Provided date for comparison + * + * @return double representing the number of seconds + */ +-(double)secondsLaterThan:(NSDate *)date{ + return MAX([self secondsFrom:date], 0); +} + + +#pragma mark Comparators +/** + * Returns a YES if receiver is earlier than provided comparison date, otherwise returns NO + * + * @param date NSDate - Provided date for comparison + * + * @return BOOL representing comparison result + */ +-(BOOL)isEarlierThan:(NSDate *)date{ + if (self.timeIntervalSince1970 < date.timeIntervalSince1970) { + return YES; + } + return NO; +} + +/** + * Returns a YES if receiver is later than provided comparison date, otherwise returns NO + * + * @param date NSDate - Provided date for comparison + * + * @return BOOL representing comparison result + */ +-(BOOL)isLaterThan:(NSDate *)date{ + if (self.timeIntervalSince1970 > date.timeIntervalSince1970) { + return YES; + } + return NO; +} + +/** + * Returns a YES if receiver is earlier than or equal to the provided comparison date, otherwise returns NO + * + * @param date NSDate - Provided date for comparison + * + * @return BOOL representing comparison result + */ +-(BOOL)isEarlierThanOrEqualTo:(NSDate *)date{ + if (self.timeIntervalSince1970 <= date.timeIntervalSince1970) { + return YES; + } + return NO; +} + +/** + * Returns a YES if receiver is later than or equal to provided comparison date, otherwise returns NO + * + * @param date NSDate - Provided date for comparison + * + * @return BOOL representing comparison result + */ +-(BOOL)isLaterThanOrEqualTo:(NSDate *)date{ + if (self.timeIntervalSince1970 >= date.timeIntervalSince1970) { + return YES; + } + return NO; +} + +#pragma mark - Formatted Dates +#pragma mark Formatted With Style +/** + * Convenience method that returns a formatted string representing the receiver's date formatted to a given style + * + * @param style NSDateFormatterStyle - Desired date formatting style + * + * @return NSString representing the formatted date string + */ +-(NSString *)formattedDateWithStyle:(NSDateFormatterStyle)style{ + return [self formattedDateWithStyle:style timeZone:[NSTimeZone systemTimeZone] locale:[NSLocale autoupdatingCurrentLocale]]; +} + +/** + * Convenience method that returns a formatted string representing the receiver's date formatted to a given style and time zone + * + * @param style NSDateFormatterStyle - Desired date formatting style + * @param timeZone NSTimeZone - Desired time zone + * + * @return NSString representing the formatted date string + */ +-(NSString *)formattedDateWithStyle:(NSDateFormatterStyle)style timeZone:(NSTimeZone *)timeZone{ + return [self formattedDateWithStyle:style timeZone:timeZone locale:[NSLocale autoupdatingCurrentLocale]]; +} + +/** + * Convenience method that returns a formatted string representing the receiver's date formatted to a given style and locale + * + * @param style NSDateFormatterStyle - Desired date formatting style + * @param locale NSLocale - Desired locale + * + * @return NSString representing the formatted date string + */ +-(NSString *)formattedDateWithStyle:(NSDateFormatterStyle)style locale:(NSLocale *)locale{ + return [self formattedDateWithStyle:style timeZone:[NSTimeZone systemTimeZone] locale:locale]; +} + +/** + * Convenience method that returns a formatted string representing the receiver's date formatted to a given style, time zone and locale + * + * @param style NSDateFormatterStyle - Desired date formatting style + * @param timeZone NSTimeZone - Desired time zone + * @param locale NSLocale - Desired locale + * + * @return NSString representing the formatted date string + */ +-(NSString *)formattedDateWithStyle:(NSDateFormatterStyle)style timeZone:(NSTimeZone *)timeZone locale:(NSLocale *)locale{ + static NSDateFormatter *formatter = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + formatter = [[NSDateFormatter alloc] init]; + }); + + [formatter setDateStyle:style]; + [formatter setTimeZone:timeZone]; + [formatter setLocale:locale]; + return [formatter stringFromDate:self]; +} + +#pragma mark Formatted With Format +/** + * Convenience method that returns a formatted string representing the receiver's date formatted to a given date format + * + * @param format NSString - String representing the desired date format + * + * @return NSString representing the formatted date string + */ +-(NSString *)formattedDateWithFormat:(NSString *)format{ + return [self formattedDateWithFormat:format timeZone:[NSTimeZone systemTimeZone] locale:[NSLocale autoupdatingCurrentLocale]]; +} + +/** + * Convenience method that returns a formatted string representing the receiver's date formatted to a given date format and time zone + * + * @param format NSString - String representing the desired date format + * @param timeZone NSTimeZone - Desired time zone + * + * @return NSString representing the formatted date string + */ +-(NSString *)formattedDateWithFormat:(NSString *)format timeZone:(NSTimeZone *)timeZone{ + return [self formattedDateWithFormat:format timeZone:timeZone locale:[NSLocale autoupdatingCurrentLocale]]; +} + +/** + * Convenience method that returns a formatted string representing the receiver's date formatted to a given date format and locale + * + * @param format NSString - String representing the desired date format + * @param locale NSLocale - Desired locale + * + * @return NSString representing the formatted date string + */ +-(NSString *)formattedDateWithFormat:(NSString *)format locale:(NSLocale *)locale{ + return [self formattedDateWithFormat:format timeZone:[NSTimeZone systemTimeZone] locale:locale]; +} + +/** + * Convenience method that returns a formatted string representing the receiver's date formatted to a given date format, time zone and locale + * + * @param format NSString - String representing the desired date format + * @param timeZone NSTimeZone - Desired time zone + * @param locale NSLocale - Desired locale + * + * @return NSString representing the formatted date string + */ +-(NSString *)formattedDateWithFormat:(NSString *)format timeZone:(NSTimeZone *)timeZone locale:(NSLocale *)locale{ + static NSDateFormatter *formatter = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + formatter = [[NSDateFormatter alloc] init]; + }); + + [formatter setDateFormat:format]; + [formatter setTimeZone:timeZone]; + [formatter setLocale:locale]; + return [formatter stringFromDate:self]; +} + +#pragma mark - Helpers +/** + * Class method that returns whether the given year is a leap year for the Gregorian Calendar + * Returns YES if year is a leap year, otherwise returns NO + * + * @param year NSInteger - Year to evaluate + * + * @return BOOL evaluation of year + */ ++(BOOL)isLeapYear:(NSInteger)year{ + if (year%400){ + return YES; + } + else if (year%100){ + return NO; + } + else if (year%4){ + return YES; + } + + return NO; +} + +/** + * Retrieves the default calendar identifier used for all non-calendar-specified operations + * + * @return NSString - NSCalendarIdentifier + */ ++(NSString *)defaultCalendarIdentifier { + return defaultCalendarIdentifier; +} + +/** + * Sets the default calendar identifier used for all non-calendar-specified operations + * + * @param identifier NSString - NSCalendarIdentifier + */ ++ (void)setDefaultCalendarIdentifier:(NSString *)identifier { + defaultCalendarIdentifier = [identifier copy]; + implicitCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:defaultCalendarIdentifier ?: NSCalendarIdentifierGregorian]; +} + +/** + * Retrieves a default NSCalendar instance, based on the value of defaultCalendarSetting + * + * @return NSCalendar The current implicit calendar + */ ++ (NSCalendar *)implicitCalendar { + return implicitCalendar; +} + +@end diff --git a/iosApp/iosApp/Libs/MJExtension/MJExtension.h b/iosApp/iosApp/Libs/MJExtension/MJExtension.h new file mode 100644 index 0000000..f2ede55 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJExtension.h @@ -0,0 +1,27 @@ +// +// MJExtension.h +// MJExtension +// +// Created by mj on 14-1-15. +// Copyright (c) 2014年 小码哥. All rights reserved. +// + +#import +#import "NSObject+MJCoding.h" +#import "NSObject+MJProperty.h" +#import "NSObject+MJClass.h" +#import "NSObject+MJKeyValue.h" +#import "NSString+MJExtension.h" +#import "MJExtensionConst.h" + +#import "MJFoundation.h" + +//! Project version number for MJExtension. +FOUNDATION_EXPORT double MJExtensionVersionNumber; + +//! Project version string for MJExtension. +FOUNDATION_EXPORT const unsigned char MJExtensionVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/iosApp/iosApp/Libs/MJExtension/MJExtensionConst.h b/iosApp/iosApp/Libs/MJExtension/MJExtensionConst.h new file mode 100644 index 0000000..5120431 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJExtensionConst.h @@ -0,0 +1,111 @@ + +#ifndef __MJExtensionConst__H__ +#define __MJExtensionConst__H__ + +#import + +#ifndef MJ_LOCK +#define MJ_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); +#endif + +#ifndef MJ_UNLOCK +#define MJ_UNLOCK(lock) dispatch_semaphore_signal(lock); +#endif + +// 信号量 +#define MJExtensionSemaphoreCreate \ +extern dispatch_semaphore_t mje_signalSemaphore; \ +extern dispatch_once_t mje_onceTokenSemaphore; \ +dispatch_once(&mje_onceTokenSemaphore, ^{ \ + mje_signalSemaphore = dispatch_semaphore_create(1); \ +}); + +// 过期 +#define MJExtensionDeprecated(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) + +// 构建错误 +#define MJExtensionBuildError(clazz, msg) \ +NSError *error = [NSError errorWithDomain:msg code:250 userInfo:nil]; \ +[clazz setMj_error:error]; + +// 日志输出 +#ifdef DEBUG +#define MJExtensionLog(...) NSLog(__VA_ARGS__) +#else +#define MJExtensionLog(...) +#endif + +/** + * 断言 + * @param condition 条件 + * @param returnValue 返回值 + */ +#define MJExtensionAssertError(condition, returnValue, clazz, msg) \ +[clazz setMj_error:nil]; \ +if ((condition) == NO) { \ + MJExtensionBuildError(clazz, msg); \ + return returnValue;\ +} + +#define MJExtensionAssert2(condition, returnValue) \ +if ((condition) == NO) return returnValue; + +/** + * 断言 + * @param condition 条件 + */ +#define MJExtensionAssert(condition) MJExtensionAssert2(condition, ) + +/** + * 断言 + * @param param 参数 + * @param returnValue 返回值 + */ +#define MJExtensionAssertParamNotNil2(param, returnValue) \ +MJExtensionAssert2((param) != nil, returnValue) + +/** + * 断言 + * @param param 参数 + */ +#define MJExtensionAssertParamNotNil(param) MJExtensionAssertParamNotNil2(param, ) + +/** + * 打印所有的属性 + */ +#define MJLogAllIvars \ +- (NSString *)description \ +{ \ + return [self mj_keyValues].description; \ +} +#define MJExtensionLogAllProperties MJLogAllIvars + +/** 仅在 Debugger 展示所有的属性 */ +#define MJImplementDebugDescription \ +- (NSString *)debugDescription \ +{ \ +return [self mj_keyValues].debugDescription; \ +} + +/** + * 类型(属性类型) + */ +FOUNDATION_EXPORT NSString *const MJPropertyTypeInt; +FOUNDATION_EXPORT NSString *const MJPropertyTypeShort; +FOUNDATION_EXPORT NSString *const MJPropertyTypeFloat; +FOUNDATION_EXPORT NSString *const MJPropertyTypeDouble; +FOUNDATION_EXPORT NSString *const MJPropertyTypeLong; +FOUNDATION_EXPORT NSString *const MJPropertyTypeLongLong; +FOUNDATION_EXPORT NSString *const MJPropertyTypeChar; +FOUNDATION_EXPORT NSString *const MJPropertyTypeBOOL1; +FOUNDATION_EXPORT NSString *const MJPropertyTypeBOOL2; +FOUNDATION_EXPORT NSString *const MJPropertyTypePointer; + +FOUNDATION_EXPORT NSString *const MJPropertyTypeIvar; +FOUNDATION_EXPORT NSString *const MJPropertyTypeMethod; +FOUNDATION_EXPORT NSString *const MJPropertyTypeBlock; +FOUNDATION_EXPORT NSString *const MJPropertyTypeClass; +FOUNDATION_EXPORT NSString *const MJPropertyTypeSEL; +FOUNDATION_EXPORT NSString *const MJPropertyTypeId; + +#endif diff --git a/iosApp/iosApp/Libs/MJExtension/MJExtensionConst.m b/iosApp/iosApp/Libs/MJExtension/MJExtensionConst.m new file mode 100644 index 0000000..24bcca5 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJExtensionConst.m @@ -0,0 +1,27 @@ +#ifndef __MJExtensionConst__M__ +#define __MJExtensionConst__M__ + +#import + +/** + * 成员变量类型(属性类型) + */ +NSString *const MJPropertyTypeInt = @"i"; +NSString *const MJPropertyTypeShort = @"s"; +NSString *const MJPropertyTypeFloat = @"f"; +NSString *const MJPropertyTypeDouble = @"d"; +NSString *const MJPropertyTypeLong = @"l"; +NSString *const MJPropertyTypeLongLong = @"q"; +NSString *const MJPropertyTypeChar = @"c"; +NSString *const MJPropertyTypeBOOL1 = @"c"; +NSString *const MJPropertyTypeBOOL2 = @"b"; +NSString *const MJPropertyTypePointer = @"*"; + +NSString *const MJPropertyTypeIvar = @"^{objc_ivar=}"; +NSString *const MJPropertyTypeMethod = @"^{objc_method=}"; +NSString *const MJPropertyTypeBlock = @"@?"; +NSString *const MJPropertyTypeClass = @"#"; +NSString *const MJPropertyTypeSEL = @":"; +NSString *const MJPropertyTypeId = @"@"; + +#endif \ No newline at end of file diff --git a/iosApp/iosApp/Libs/MJExtension/MJFoundation.h b/iosApp/iosApp/Libs/MJExtension/MJFoundation.h new file mode 100644 index 0000000..f2c1967 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJFoundation.h @@ -0,0 +1,16 @@ +// +// MJFoundation.h +// MJExtensionExample +// +// Created by MJ Lee on 14/7/16. +// Copyright (c) 2014年 小码哥. All rights reserved. +// + +#import + +@interface MJFoundation : NSObject + ++ (BOOL)isClassFromFoundation:(Class)c; ++ (BOOL)isFromNSObjectProtocolProperty:(NSString *)propertyName; + +@end diff --git a/iosApp/iosApp/Libs/MJExtension/MJFoundation.m b/iosApp/iosApp/Libs/MJExtension/MJFoundation.m new file mode 100644 index 0000000..31e107d --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJFoundation.m @@ -0,0 +1,70 @@ +// +// MJFoundation.m +// MJExtensionExample +// +// Created by MJ Lee on 14/7/16. +// Copyright (c) 2014年 小码哥. All rights reserved. +// + +#import "MJFoundation.h" +#import "MJExtensionConst.h" +#import +#import "objc/runtime.h" + +@implementation MJFoundation + ++ (BOOL)isClassFromFoundation:(Class)c +{ + if (c == [NSObject class] || c == [NSManagedObject class]) return YES; + + static NSSet *foundationClasses; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // 集合中没有NSObject,因为几乎所有的类都是继承自NSObject,具体是不是NSObject需要特殊判断 + foundationClasses = [NSSet setWithObjects: + [NSURL class], + [NSDate class], + [NSValue class], + [NSData class], + [NSError class], + [NSArray class], + [NSDictionary class], + [NSString class], + [NSAttributedString class], nil]; + }); + + __block BOOL result = NO; + [foundationClasses enumerateObjectsUsingBlock:^(Class foundationClass, BOOL *stop) { + if ([c isSubclassOfClass:foundationClass]) { + result = YES; + *stop = YES; + } + }]; + return result; +} + ++ (BOOL)isFromNSObjectProtocolProperty:(NSString *)propertyName +{ + if (!propertyName) return NO; + + static NSSet *objectProtocolPropertyNames; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + unsigned int count = 0; + objc_property_t *propertyList = protocol_copyPropertyList(@protocol(NSObject), &count); + NSMutableSet *propertyNames = [NSMutableSet setWithCapacity:count]; + for (int i = 0; i < count; i++) { + objc_property_t property = propertyList[i]; + NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding]; + if (propertyName) { + [propertyNames addObject:propertyName]; + } + } + objectProtocolPropertyNames = [propertyNames copy]; + free(propertyList); + }); + + return [objectProtocolPropertyNames containsObject:propertyName]; +} + +@end diff --git a/iosApp/iosApp/Libs/MJExtension/MJProperty.h b/iosApp/iosApp/Libs/MJExtension/MJProperty.h new file mode 100644 index 0000000..90ac6bc --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJProperty.h @@ -0,0 +1,53 @@ +// +// MJProperty.h +// MJExtensionExample +// +// Created by MJ Lee on 15/4/17. +// Copyright (c) 2015年 小码哥. All rights reserved. +// 包装一个成员属性 + +#import +#import +#import "MJPropertyType.h" +#import "MJPropertyKey.h" + +/** + * 包装一个成员 + */ +@interface MJProperty : NSObject +/** 成员属性 */ +@property (nonatomic, assign) objc_property_t property; +/** 成员属性的名字 */ +@property (nonatomic, readonly) NSString *name; + +/** 成员属性的类型 */ +@property (nonatomic, readonly) MJPropertyType *type; +/** 成员属性来源于哪个类(可能是父类) */ +@property (nonatomic, assign) Class srcClass; + +/**** 同一个成员属性 - 父类和子类的行为可能不一致(originKey、propertyKeys、objectClassInArray) ****/ +/** 设置最原始的key */ +- (void)setOriginKey:(id)originKey forClass:(Class)c; +/** 对应着字典中的多级key(里面存放的数组,数组里面都是MJPropertyKey对象) */ +- (NSArray *)propertyKeysForClass:(Class)c; + +/** 模型数组中的模型类型 */ +- (void)setObjectClassInArray:(Class)objectClass forClass:(Class)c; +- (Class)objectClassInArrayForClass:(Class)c; +/**** 同一个成员变量 - 父类和子类的行为可能不一致(key、keys、objectClassInArray) ****/ + +/** + * 设置object的成员变量值 + */ +- (void)setValue:(id)value forObject:(id)object; +/** + * 得到object的成员属性值 + */ +- (id)valueForObject:(id)object; + +/** + * 初始化 + */ ++ (instancetype)cachedPropertyWithProperty:(objc_property_t)property; + +@end diff --git a/iosApp/iosApp/Libs/MJExtension/MJProperty.m b/iosApp/iosApp/Libs/MJExtension/MJProperty.m new file mode 100644 index 0000000..dcda032 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJProperty.m @@ -0,0 +1,211 @@ +// +// MJProperty.m +// MJExtensionExample +// +// Created by MJ Lee on 15/4/17. +// Copyright (c) 2015年 小码哥. All rights reserved. +// + +#import "MJProperty.h" +#import "MJFoundation.h" +#import "MJExtensionConst.h" +#import +#include "TargetConditionals.h" + +@interface MJProperty() +@property (strong, nonatomic) NSMutableDictionary *propertyKeysDict; +@property (strong, nonatomic) NSMutableDictionary *objectClassInArrayDict; +@property (strong, nonatomic) dispatch_semaphore_t propertyKeysLock; +@property (strong, nonatomic) dispatch_semaphore_t objectClassInArrayLock; +@end + +@implementation MJProperty + +#pragma mark - 初始化 +- (instancetype)init +{ + if (self = [super init]) { + _propertyKeysDict = [NSMutableDictionary dictionary]; + _objectClassInArrayDict = [NSMutableDictionary dictionary]; + _propertyKeysLock = dispatch_semaphore_create(1); + _objectClassInArrayLock = dispatch_semaphore_create(1); + } + return self; +} + +#pragma mark - 缓存 ++ (instancetype)cachedPropertyWithProperty:(objc_property_t)property +{ + MJProperty *propertyObj = objc_getAssociatedObject(self, property); + if (propertyObj == nil) { + propertyObj = [[self alloc] init]; + propertyObj.property = property; + objc_setAssociatedObject(self, property, propertyObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return propertyObj; +} + +#pragma mark - 公共方法 +- (void)setProperty:(objc_property_t)property +{ + _property = property; + + MJExtensionAssertParamNotNil(property); + + // 1.属性名 + _name = @(property_getName(property)); + + // 2.成员类型 + NSString *attrs = @(property_getAttributes(property)); + NSUInteger dotLoc = [attrs rangeOfString:@","].location; + NSString *code = nil; + NSUInteger loc = 1; + if (dotLoc == NSNotFound) { // 没有, + code = [attrs substringFromIndex:loc]; + } else { + code = [attrs substringWithRange:NSMakeRange(loc, dotLoc - loc)]; + } + _type = [MJPropertyType cachedTypeWithCode:code]; +} + +/** + * 获得成员变量的值 + */ +- (id)valueForObject:(id)object +{ + if (self.type.KVCDisabled) return [NSNull null]; + + id value = [object valueForKey:self.name]; + + // 32位BOOL类型转换json后成Int类型 + /** https://github.com/CoderMJLee/MJExtension/issues/545 */ + // 32 bit device OR 32 bit Simulator +#if defined(__arm__) || (TARGET_OS_SIMULATOR && !__LP64__) + if (self.type.isBoolType) { + value = @([(NSNumber *)value boolValue]); + } +#endif + + return value; +} + +/** + * 设置成员变量的值 + */ +- (void)setValue:(id)value forObject:(id)object +{ + if (self.type.KVCDisabled || value == nil) return; + [object setValue:value forKey:self.name]; +} + +/** + * 通过字符串key创建对应的keys + */ +- (NSArray *)propertyKeysWithStringKey:(NSString *)stringKey +{ + if (stringKey.length == 0) return nil; + + NSMutableArray *propertyKeys = [NSMutableArray array]; + // 如果有多级映射 + NSArray *oldKeys = [stringKey componentsSeparatedByString:@"."]; + + for (NSString *oldKey in oldKeys) { + NSUInteger start = [oldKey rangeOfString:@"["].location; + if (start != NSNotFound) { // 有索引的key + NSString *prefixKey = [oldKey substringToIndex:start]; + NSString *indexKey = prefixKey; + if (prefixKey.length) { + MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init]; + propertyKey.name = prefixKey; + [propertyKeys addObject:propertyKey]; + + indexKey = [oldKey stringByReplacingOccurrencesOfString:prefixKey withString:@""]; + } + + /** 解析索引 **/ + // 元素 + NSArray *cmps = [[indexKey stringByReplacingOccurrencesOfString:@"[" withString:@""] componentsSeparatedByString:@"]"]; + for (NSInteger i = 0; i + +typedef enum { + MJPropertyKeyTypeDictionary = 0, // 字典的key + MJPropertyKeyTypeArray // 数组的key +} MJPropertyKeyType; + +/** + * 属性的key + */ +@interface MJPropertyKey : NSObject +/** key的名字 */ +@property (copy, nonatomic) NSString *name; +/** key的种类,可能是@"10",可能是@"age" */ +@property (assign, nonatomic) MJPropertyKeyType type; + +/** + * 根据当前的key,也就是name,从object(字典或者数组)中取值 + */ +- (id)valueInObject:(id)object; + +@end diff --git a/iosApp/iosApp/Libs/MJExtension/MJPropertyKey.m b/iosApp/iosApp/Libs/MJExtension/MJPropertyKey.m new file mode 100644 index 0000000..438d019 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJPropertyKey.m @@ -0,0 +1,25 @@ +// +// MJPropertyKey.m +// MJExtensionExample +// +// Created by MJ Lee on 15/8/11. +// Copyright (c) 2015年 小码哥. All rights reserved. +// + +#import "MJPropertyKey.h" + +@implementation MJPropertyKey + +- (id)valueInObject:(id)object +{ + if ([object isKindOfClass:[NSDictionary class]] && self.type == MJPropertyKeyTypeDictionary) { + return object[self.name]; + } else if ([object isKindOfClass:[NSArray class]] && self.type == MJPropertyKeyTypeArray) { + NSArray *array = object; + NSUInteger index = self.name.intValue; + if (index < array.count) return array[index]; + return nil; + } + return nil; +} +@end diff --git a/iosApp/iosApp/Libs/MJExtension/MJPropertyType.h b/iosApp/iosApp/Libs/MJExtension/MJPropertyType.h new file mode 100755 index 0000000..8c53f27 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJPropertyType.h @@ -0,0 +1,39 @@ +// +// MJPropertyType.h +// MJExtension +// +// Created by mj on 14-1-15. +// Copyright (c) 2014年 小码哥. All rights reserved. +// 包装一种类型 + +#import + +/** + * 包装一种类型 + */ +@interface MJPropertyType : NSObject +/** 类型标识符 */ +@property (nonatomic, copy) NSString *code; + +/** 是否为id类型 */ +@property (nonatomic, readonly, getter=isIdType) BOOL idType; + +/** 是否为基本数字类型:int、float等 */ +@property (nonatomic, readonly, getter=isNumberType) BOOL numberType; + +/** 是否为BOOL类型 */ +@property (nonatomic, readonly, getter=isBoolType) BOOL boolType; + +/** 对象类型(如果是基本数据类型,此值为nil) */ +@property (nonatomic, readonly) Class typeClass; + +/** 类型是否来自于Foundation框架,比如NSString、NSArray */ +@property (nonatomic, readonly, getter = isFromFoundation) BOOL fromFoundation; +/** 类型是否不支持KVC */ +@property (nonatomic, readonly, getter = isKVCDisabled) BOOL KVCDisabled; + +/** + * 获得缓存的类型对象 + */ ++ (instancetype)cachedTypeWithCode:(NSString *)code; +@end \ No newline at end of file diff --git a/iosApp/iosApp/Libs/MJExtension/MJPropertyType.m b/iosApp/iosApp/Libs/MJExtension/MJPropertyType.m new file mode 100755 index 0000000..77d6b30 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/MJPropertyType.m @@ -0,0 +1,71 @@ +// +// MJPropertyType.m +// MJExtension +// +// Created by mj on 14-1-15. +// Copyright (c) 2014年 小码哥. All rights reserved. +// + +#import "MJPropertyType.h" +#import "MJExtension.h" +#import "MJFoundation.h" +#import "MJExtensionConst.h" + +@implementation MJPropertyType + ++ (instancetype)cachedTypeWithCode:(NSString *)code +{ + MJExtensionAssertParamNotNil2(code, nil); + + static NSMutableDictionary *types; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + types = [NSMutableDictionary dictionary]; + }); + + MJPropertyType *type = types[code]; + if (type == nil) { + type = [[self alloc] init]; + type.code = code; + types[code] = type; + } + return type; +} + +#pragma mark - 公共方法 +- (void)setCode:(NSString *)code +{ + _code = code; + + MJExtensionAssertParamNotNil(code); + + if ([code isEqualToString:MJPropertyTypeId]) { + _idType = YES; + } else if (code.length == 0) { + _KVCDisabled = YES; + } else if (code.length > 3 && [code hasPrefix:@"@\""]) { + // 去掉@"和",截取中间的类型名称 + _code = [code substringWithRange:NSMakeRange(2, code.length - 3)]; + _typeClass = NSClassFromString(_code); + _fromFoundation = [MJFoundation isClassFromFoundation:_typeClass]; + _numberType = [_typeClass isSubclassOfClass:[NSNumber class]]; + + } else if ([code isEqualToString:MJPropertyTypeSEL] || + [code isEqualToString:MJPropertyTypeIvar] || + [code isEqualToString:MJPropertyTypeMethod]) { + _KVCDisabled = YES; + } + + // 是否为数字类型 + NSString *lowerCode = _code.lowercaseString; + NSArray *numberTypes = @[MJPropertyTypeInt, MJPropertyTypeShort, MJPropertyTypeBOOL1, MJPropertyTypeBOOL2, MJPropertyTypeFloat, MJPropertyTypeDouble, MJPropertyTypeLong, MJPropertyTypeLongLong, MJPropertyTypeChar]; + if ([numberTypes containsObject:lowerCode]) { + _numberType = YES; + + if ([lowerCode isEqualToString:MJPropertyTypeBOOL1] + || [lowerCode isEqualToString:MJPropertyTypeBOOL2]) { + _boolType = YES; + } + } +} +@end diff --git a/iosApp/iosApp/Libs/MJExtension/NSObject+MJClass.h b/iosApp/iosApp/Libs/MJExtension/NSObject+MJClass.h new file mode 100644 index 0000000..260c8fc --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSObject+MJClass.h @@ -0,0 +1,90 @@ +// +// NSObject+MJClass.h +// MJExtensionExample +// +// Created by MJ Lee on 15/8/11. +// Copyright (c) 2015年 小码哥. All rights reserved. +// + +#import + +/** + * 遍历所有类的block(父类) + */ +typedef void (^MJClassesEnumeration)(Class c, BOOL *stop); + +/** 这个数组中的属性名才会进行字典和模型的转换 */ +typedef NSArray * (^MJAllowedPropertyNames)(void); +/** 这个数组中的属性名才会进行归档 */ +typedef NSArray * (^MJAllowedCodingPropertyNames)(void); + +/** 这个数组中的属性名将会被忽略:不进行字典和模型的转换 */ +typedef NSArray * (^MJIgnoredPropertyNames)(void); +/** 这个数组中的属性名将会被忽略:不进行归档 */ +typedef NSArray * (^MJIgnoredCodingPropertyNames)(void); + +/** + * 类相关的扩展 + */ +@interface NSObject (MJClass) +/** + * 遍历所有的类 + */ ++ (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration; ++ (void)mj_enumerateAllClasses:(MJClassesEnumeration)enumeration; + +#pragma mark - 属性白名单配置 +/** + * 这个数组中的属性名才会进行字典和模型的转换 + * + * @param allowedPropertyNames 这个数组中的属性名才会进行字典和模型的转换 + */ ++ (void)mj_setupAllowedPropertyNames:(MJAllowedPropertyNames)allowedPropertyNames; + +/** + * 这个数组中的属性名才会进行字典和模型的转换 + */ ++ (NSMutableArray *)mj_totalAllowedPropertyNames; + +#pragma mark - 属性黑名单配置 +/** + * 这个数组中的属性名将会被忽略:不进行字典和模型的转换 + * + * @param ignoredPropertyNames 这个数组中的属性名将会被忽略:不进行字典和模型的转换 + */ ++ (void)mj_setupIgnoredPropertyNames:(MJIgnoredPropertyNames)ignoredPropertyNames; + +/** + * 这个数组中的属性名将会被忽略:不进行字典和模型的转换 + */ ++ (NSMutableArray *)mj_totalIgnoredPropertyNames; + +#pragma mark - 归档属性白名单配置 +/** + * 这个数组中的属性名才会进行归档 + * + * @param allowedCodingPropertyNames 这个数组中的属性名才会进行归档 + */ ++ (void)mj_setupAllowedCodingPropertyNames:(MJAllowedCodingPropertyNames)allowedCodingPropertyNames; + +/** + * 这个数组中的属性名才会进行字典和模型的转换 + */ ++ (NSMutableArray *)mj_totalAllowedCodingPropertyNames; + +#pragma mark - 归档属性黑名单配置 +/** + * 这个数组中的属性名将会被忽略:不进行归档 + * + * @param ignoredCodingPropertyNames 这个数组中的属性名将会被忽略:不进行归档 + */ ++ (void)mj_setupIgnoredCodingPropertyNames:(MJIgnoredCodingPropertyNames)ignoredCodingPropertyNames; + +/** + * 这个数组中的属性名将会被忽略:不进行归档 + */ ++ (NSMutableArray *)mj_totalIgnoredCodingPropertyNames; + +#pragma mark - 内部使用 ++ (void)mj_setupBlockReturnValue:(id (^)(void))block key:(const char *)key; +@end diff --git a/iosApp/iosApp/Libs/MJExtension/NSObject+MJClass.m b/iosApp/iosApp/Libs/MJExtension/NSObject+MJClass.m new file mode 100644 index 0000000..c476751 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSObject+MJClass.m @@ -0,0 +1,174 @@ +// +// NSObject+MJClass.m +// MJExtensionExample +// +// Created by MJ Lee on 15/8/11. +// Copyright (c) 2015年 小码哥. All rights reserved. +// + +#import "NSObject+MJClass.h" +#import "NSObject+MJCoding.h" +#import "NSObject+MJKeyValue.h" +#import "MJFoundation.h" +#import + +static const char MJAllowedPropertyNamesKey = '\0'; +static const char MJIgnoredPropertyNamesKey = '\0'; +static const char MJAllowedCodingPropertyNamesKey = '\0'; +static const char MJIgnoredCodingPropertyNamesKey = '\0'; + +@implementation NSObject (MJClass) + ++ (NSMutableDictionary *)mj_classDictForKey:(const void *)key +{ + static NSMutableDictionary *allowedPropertyNamesDict; + static NSMutableDictionary *ignoredPropertyNamesDict; + static NSMutableDictionary *allowedCodingPropertyNamesDict; + static NSMutableDictionary *ignoredCodingPropertyNamesDict; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + allowedPropertyNamesDict = [NSMutableDictionary dictionary]; + ignoredPropertyNamesDict = [NSMutableDictionary dictionary]; + allowedCodingPropertyNamesDict = [NSMutableDictionary dictionary]; + ignoredCodingPropertyNamesDict = [NSMutableDictionary dictionary]; + }); + + if (key == &MJAllowedPropertyNamesKey) return allowedPropertyNamesDict; + if (key == &MJIgnoredPropertyNamesKey) return ignoredPropertyNamesDict; + if (key == &MJAllowedCodingPropertyNamesKey) return allowedCodingPropertyNamesDict; + if (key == &MJIgnoredCodingPropertyNamesKey) return ignoredCodingPropertyNamesDict; + return nil; +} + ++ (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration +{ + // 1.没有block就直接返回 + if (enumeration == nil) return; + + // 2.停止遍历的标记 + BOOL stop = NO; + + // 3.当前正在遍历的类 + Class c = self; + + // 4.开始遍历每一个类 + while (c && !stop) { + // 4.1.执行操作 + enumeration(c, &stop); + + // 4.2.获得父类 + c = class_getSuperclass(c); + + if ([MJFoundation isClassFromFoundation:c]) break; + } +} + ++ (void)mj_enumerateAllClasses:(MJClassesEnumeration)enumeration +{ + // 1.没有block就直接返回 + if (enumeration == nil) return; + + // 2.停止遍历的标记 + BOOL stop = NO; + + // 3.当前正在遍历的类 + Class c = self; + + // 4.开始遍历每一个类 + while (c && !stop) { + // 4.1.执行操作 + enumeration(c, &stop); + + // 4.2.获得父类 + c = class_getSuperclass(c); + } +} + +#pragma mark - 属性黑名单配置 ++ (void)mj_setupIgnoredPropertyNames:(MJIgnoredPropertyNames)ignoredPropertyNames +{ + [self mj_setupBlockReturnValue:ignoredPropertyNames key:&MJIgnoredPropertyNamesKey]; +} + ++ (NSMutableArray *)mj_totalIgnoredPropertyNames +{ + return [self mj_totalObjectsWithSelector:@selector(mj_ignoredPropertyNames) key:&MJIgnoredPropertyNamesKey]; +} + +#pragma mark - 归档属性黑名单配置 ++ (void)mj_setupIgnoredCodingPropertyNames:(MJIgnoredCodingPropertyNames)ignoredCodingPropertyNames +{ + [self mj_setupBlockReturnValue:ignoredCodingPropertyNames key:&MJIgnoredCodingPropertyNamesKey]; +} + ++ (NSMutableArray *)mj_totalIgnoredCodingPropertyNames +{ + return [self mj_totalObjectsWithSelector:@selector(mj_ignoredCodingPropertyNames) key:&MJIgnoredCodingPropertyNamesKey]; +} + +#pragma mark - 属性白名单配置 ++ (void)mj_setupAllowedPropertyNames:(MJAllowedPropertyNames)allowedPropertyNames; +{ + [self mj_setupBlockReturnValue:allowedPropertyNames key:&MJAllowedPropertyNamesKey]; +} + ++ (NSMutableArray *)mj_totalAllowedPropertyNames +{ + return [self mj_totalObjectsWithSelector:@selector(mj_allowedPropertyNames) key:&MJAllowedPropertyNamesKey]; +} + +#pragma mark - 归档属性白名单配置 ++ (void)mj_setupAllowedCodingPropertyNames:(MJAllowedCodingPropertyNames)allowedCodingPropertyNames +{ + [self mj_setupBlockReturnValue:allowedCodingPropertyNames key:&MJAllowedCodingPropertyNamesKey]; +} + ++ (NSMutableArray *)mj_totalAllowedCodingPropertyNames +{ + return [self mj_totalObjectsWithSelector:@selector(mj_allowedCodingPropertyNames) key:&MJAllowedCodingPropertyNamesKey]; +} + +#pragma mark - block和方法处理:存储block的返回值 ++ (void)mj_setupBlockReturnValue:(id (^)(void))block key:(const char *)key { + MJExtensionSemaphoreCreate + MJ_LOCK(mje_signalSemaphore); + if (block) { + objc_setAssociatedObject(self, key, block(), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } else { + objc_setAssociatedObject(self, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + // 清空数据 + [[self mj_classDictForKey:key] removeAllObjects]; + MJ_UNLOCK(mje_signalSemaphore); +} + ++ (NSMutableArray *)mj_totalObjectsWithSelector:(SEL)selector key:(const char *)key +{ + MJExtensionSemaphoreCreate + MJ_LOCK(mje_signalSemaphore); + NSMutableArray *array = [self mj_classDictForKey:key][NSStringFromClass(self)]; + if (array == nil) { + // 创建、存储 + [self mj_classDictForKey:key][NSStringFromClass(self)] = array = [NSMutableArray array]; + + if ([self respondsToSelector:selector]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + NSArray *subArray = [self performSelector:selector]; +#pragma clang diagnostic pop + if (subArray) { + [array addObjectsFromArray:subArray]; + } + } + + [self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) { + NSArray *subArray = objc_getAssociatedObject(c, key); + [array addObjectsFromArray:subArray]; + }]; + } + MJ_UNLOCK(mje_signalSemaphore); + return array; +} +@end diff --git a/iosApp/iosApp/Libs/MJExtension/NSObject+MJCoding.h b/iosApp/iosApp/Libs/MJExtension/NSObject+MJCoding.h new file mode 100755 index 0000000..aeeb4eb --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSObject+MJCoding.h @@ -0,0 +1,66 @@ +// +// NSObject+MJCoding.h +// MJExtension +// +// Created by mj on 14-1-15. +// Copyright (c) 2014年 小码哥. All rights reserved. +// + +#import +#import "MJExtensionConst.h" + +/** + * Codeing协议 + */ +@protocol MJCoding +@optional +/** + * 这个数组中的属性名才会进行归档 + */ ++ (NSArray *)mj_allowedCodingPropertyNames; +/** + * 这个数组中的属性名将会被忽略:不进行归档 + */ ++ (NSArray *)mj_ignoredCodingPropertyNames; +@end + +@interface NSObject (MJCoding) +/** + * 解码(从文件中解析对象) + */ +- (void)mj_decode:(NSCoder *)decoder; +/** + * 编码(将对象写入文件中) + */ +- (void)mj_encode:(NSCoder *)encoder; +@end + +/** + 归档的实现 + */ +#define MJCodingImplementation \ +- (id)initWithCoder:(NSCoder *)decoder \ +{ \ +if (self = [super init]) { \ +[self mj_decode:decoder]; \ +} \ +return self; \ +} \ +\ +- (void)encodeWithCoder:(NSCoder *)encoder \ +{ \ +[self mj_encode:encoder]; \ +}\ + +#define MJExtensionCodingImplementation MJCodingImplementation + +#define MJSecureCodingImplementation(CLASS, FLAG) \ +@interface CLASS (MJSecureCoding) \ +@end \ +@implementation CLASS (MJSecureCoding) \ +MJCodingImplementation \ ++ (BOOL)supportsSecureCoding { \ +return FLAG; \ +} \ +@end \ + diff --git a/iosApp/iosApp/Libs/MJExtension/NSObject+MJCoding.m b/iosApp/iosApp/Libs/MJExtension/NSObject+MJCoding.m new file mode 100755 index 0000000..614514a --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSObject+MJCoding.m @@ -0,0 +1,59 @@ +// +// NSObject+MJCoding.m +// MJExtension +// +// Created by mj on 14-1-15. +// Copyright (c) 2014年 小码哥. All rights reserved. +// + +#import "NSObject+MJCoding.h" +#import "NSObject+MJClass.h" +#import "NSObject+MJProperty.h" +#import "MJProperty.h" + +@implementation NSObject (MJCoding) + +- (void)mj_encode:(NSCoder *)encoder +{ + Class clazz = [self class]; + + NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames]; + NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames]; + + [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) { + // 检测是否被忽略 + if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return; + if ([ignoredCodingPropertyNames containsObject:property.name]) return; + + id value = [property valueForObject:self]; + if (value == nil) return; + [encoder encodeObject:value forKey:property.name]; + }]; +} + +- (void)mj_decode:(NSCoder *)decoder +{ + Class clazz = [self class]; + + NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames]; + NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames]; + + [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) { + // 检测是否被忽略 + if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return; + if ([ignoredCodingPropertyNames containsObject:property.name]) return; + + // fixed `-[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSNumber'(This will be disallowed in the future.)` warning. + Class genericClass = [property objectClassInArrayForClass:property.srcClass]; + // If genericClass exists, property.type.typeClass would be a collection type(Array, Set, Dictionary). This scenario([obj, nil, obj, nil]) would not happened. + NSSet *classes = [NSSet setWithObjects:NSNumber.class, + property.type.typeClass, genericClass, nil]; + id value = [decoder decodeObjectOfClasses:classes forKey:property.name]; + if (value == nil) { // 兼容以前的MJExtension版本 + value = [decoder decodeObjectForKey:[@"_" stringByAppendingString:property.name]]; + } + if (value == nil) return; + [property setValue:value forObject:self]; + }]; +} +@end diff --git a/iosApp/iosApp/Libs/MJExtension/NSObject+MJKeyValue.h b/iosApp/iosApp/Libs/MJExtension/NSObject+MJKeyValue.h new file mode 100755 index 0000000..3609357 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSObject+MJKeyValue.h @@ -0,0 +1,194 @@ +// +// NSObject+MJKeyValue.h +// MJExtension +// +// Created by mj on 13-8-24. +// Copyright (c) 2013年 小码哥. All rights reserved. +// + +#import +#import "MJExtensionConst.h" +#import +#import "MJProperty.h" + +/** + * KeyValue协议 + */ +@protocol MJKeyValue +@optional +/** + * 只有这个数组中的属性名才允许进行字典和模型的转换 + */ ++ (NSArray *)mj_allowedPropertyNames; + +/** + * 这个数组中的属性名将会被忽略:不进行字典和模型的转换 + */ ++ (NSArray *)mj_ignoredPropertyNames; + +/** + * 将属性名换为其他key去字典中取值 + * + * @return 字典中的key是属性名,value是从字典中取值用的key + */ ++ (NSDictionary *)mj_replacedKeyFromPropertyName; + +/** + * 将属性名换为其他key去字典中取值 + * + * @return 从字典中取值用的key + */ ++ (id)mj_replacedKeyFromPropertyName121:(NSString *)propertyName; + +/** + * 数组中需要转换的模型类 + * + * @return 字典中的key是数组属性名,value是数组中存放模型的Class(Class类型或者NSString类型) + */ ++ (NSDictionary *)mj_objectClassInArray; + + +/** 特殊地区在字符串格式化数字时使用 */ ++ (NSLocale *)mj_numberLocale; + +/** + * 旧值换新值,用于过滤字典中的值 + * + * @param oldValue 旧值 + * + * @return 新值 + */ +- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property; + +/** + * 当字典转模型完毕时调用 + */ +- (void)mj_keyValuesDidFinishConvertingToObject MJExtensionDeprecated("请使用`mj_didConvertToObjectWithKeyValues:`替代"); +- (void)mj_keyValuesDidFinishConvertingToObject:(NSDictionary *)keyValues MJExtensionDeprecated("请使用`mj_didConvertToObjectWithKeyValues:`替代"); +- (void)mj_didConvertToObjectWithKeyValues:(NSDictionary *)keyValues; + +/** + * 当模型转字典完毕时调用 + */ +- (void)mj_objectDidFinishConvertingToKeyValues MJExtensionDeprecated("请使用`mj_objectDidConvertToKeyValues:`替代"); +- (void)mj_objectDidConvertToKeyValues:(NSMutableDictionary *)keyValues; + +@end + +@interface NSObject (MJKeyValue) +#pragma mark - 类方法 +/** + * 字典转模型过程中遇到的错误 + */ ++ (NSError *)mj_error; + +/** + * 模型转字典时,字典的key是否参考replacedKeyFromPropertyName等方法(父类设置了,子类也会继承下来) + */ ++ (void)mj_referenceReplacedKeyWhenCreatingKeyValues:(BOOL)reference; + +#pragma mark - 对象方法 +/** + * 将字典的键值对转成模型属性 + * @param keyValues 字典(可以是NSDictionary、NSData、NSString) + */ +- (instancetype)mj_setKeyValues:(id)keyValues; + +/** + * 将字典的键值对转成模型属性 + * @param keyValues 字典(可以是NSDictionary、NSData、NSString) + * @param context CoreData上下文 + */ +- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context; + +/** + * 将模型转成字典 + * @return 字典 + */ +- (NSMutableDictionary *)mj_keyValues; +- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys; +- (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys; + +/** + * 通过模型数组来创建一个字典数组 + * @param objectArray 模型数组 + * @return 字典数组 + */ ++ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray; ++ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys; ++ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray ignoredKeys:(NSArray *)ignoredKeys; + +#pragma mark - 字典转模型 +/** + * 通过字典来创建一个模型 + * @param keyValues 字典(可以是NSDictionary、NSData、NSString) + * @return 新建的对象 + */ ++ (instancetype)mj_objectWithKeyValues:(id)keyValues; + +/** + * 通过字典来创建一个CoreData模型 + * @param keyValues 字典(可以是NSDictionary、NSData、NSString) + * @param context CoreData上下文 + * @return 新建的对象 + */ ++ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context; + +/** + * 通过plist来创建一个模型 + * @param filename 文件名(仅限于mainBundle中的文件) + * @return 新建的对象 + */ ++ (instancetype)mj_objectWithFilename:(NSString *)filename; + +/** + * 通过plist来创建一个模型 + * @param file 文件全路径 + * @return 新建的对象 + */ ++ (instancetype)mj_objectWithFile:(NSString *)file; + +#pragma mark - 字典数组转模型数组 +/** + * 通过字典数组来创建一个模型数组 + * @param keyValuesArray 字典数组(可以是NSDictionary、NSData、NSString) + * @return 模型数组 + */ ++ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray; + +/** + * 通过字典数组来创建一个模型数组 + * @param keyValuesArray 字典数组(可以是NSDictionary、NSData、NSString) + * @param context CoreData上下文 + * @return 模型数组 + */ ++ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context; + +/** + * 通过plist来创建一个模型数组 + * @param filename 文件名(仅限于mainBundle中的文件) + * @return 模型数组 + */ ++ (NSMutableArray *)mj_objectArrayWithFilename:(NSString *)filename; + +/** + * 通过plist来创建一个模型数组 + * @param file 文件全路径 + * @return 模型数组 + */ ++ (NSMutableArray *)mj_objectArrayWithFile:(NSString *)file; + +#pragma mark - 转换为JSON +/** + * 转换为JSON Data + */ +- (NSData *)mj_JSONData; +/** + * 转换为字典或者数组 + */ +- (id)mj_JSONObject; +/** + * 转换为JSON 字符串 + */ +- (NSString *)mj_JSONString; +@end diff --git a/iosApp/iosApp/Libs/MJExtension/NSObject+MJKeyValue.m b/iosApp/iosApp/Libs/MJExtension/NSObject+MJKeyValue.m new file mode 100755 index 0000000..c48e003 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSObject+MJKeyValue.m @@ -0,0 +1,524 @@ +// +// NSObject+MJKeyValue.m +// MJExtension +// +// Created by mj on 13-8-24. +// Copyright (c) 2013年 小码哥. All rights reserved. +// + +#import "NSObject+MJKeyValue.h" +#import "NSObject+MJProperty.h" +#import "NSString+MJExtension.h" +#import "MJProperty.h" +#import "MJPropertyType.h" +#import "MJExtensionConst.h" +#import "MJFoundation.h" +#import "NSString+MJExtension.h" +#import "NSObject+MJClass.h" + +@implementation NSDecimalNumber(MJKeyValue) + +- (id)mj_standardValueWithTypeCode:(NSString *)typeCode { + // 由于这里涉及到编译器问题, 暂时保留 Long, 实际上在 64 位系统上, 这 2 个精度范围相同, + // 32 位略有不同, 其余都可使用 Double 进行强转不丢失精度 + if ([typeCode isEqualToString:MJPropertyTypeLongLong]) { + return @(self.longLongValue); + } else if ([typeCode isEqualToString:MJPropertyTypeLongLong.uppercaseString]) { + return @(self.unsignedLongLongValue); + } else if ([typeCode isEqualToString:MJPropertyTypeLong]) { + return @(self.longValue); + } else if ([typeCode isEqualToString:MJPropertyTypeLong.uppercaseString]) { + return @(self.unsignedLongValue); + } else { + return @(self.doubleValue); + } +} + +@end + +@implementation NSObject (MJKeyValue) + +#pragma mark - 错误 +static const char MJErrorKey = '\0'; ++ (NSError *)mj_error +{ + return objc_getAssociatedObject(self, &MJErrorKey); +} + ++ (void)setMj_error:(NSError *)error +{ + objc_setAssociatedObject(self, &MJErrorKey, error, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +#pragma mark - 模型 -> 字典时的参考 +/** 模型转字典时,字典的key是否参考replacedKeyFromPropertyName等方法(父类设置了,子类也会继承下来) */ +static const char MJReferenceReplacedKeyWhenCreatingKeyValuesKey = '\0'; + ++ (void)mj_referenceReplacedKeyWhenCreatingKeyValues:(BOOL)reference +{ + objc_setAssociatedObject(self, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey, @(reference), OBJC_ASSOCIATION_ASSIGN); +} + ++ (BOOL)mj_isReferenceReplacedKeyWhenCreatingKeyValues +{ + __block id value = objc_getAssociatedObject(self, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey); + if (!value) { + [self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) { + value = objc_getAssociatedObject(c, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey); + + if (value) *stop = YES; + }]; + } + return [value boolValue]; +} + +#pragma mark - --常用的对象-- ++ (void)load +{ + // 默认设置 + [self mj_referenceReplacedKeyWhenCreatingKeyValues:YES]; +} + +#pragma mark - --公共方法-- +#pragma mark - 字典 -> 模型 +- (instancetype)mj_setKeyValues:(id)keyValues +{ + return [self mj_setKeyValues:keyValues context:nil]; +} + +/** + 核心代码: + */ +- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context +{ + // 获得JSON对象 + keyValues = [keyValues mj_JSONObject]; + + MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典"); + + Class clazz = [self class]; + NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames]; + NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames]; + + NSLocale *numberLocale = nil; + if ([self.class respondsToSelector:@selector(mj_numberLocale)]) { + numberLocale = self.class.mj_numberLocale; + } + + //通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。 + [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) { + @try { + // 0.检测是否被忽略 + if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return; + if ([ignoredPropertyNames containsObject:property.name]) return; + + // 1.取出属性值 + id value; + NSArray *propertyKeyses = [property propertyKeysForClass:clazz]; + for (NSArray *propertyKeys in propertyKeyses) { + value = keyValues; + for (MJPropertyKey *propertyKey in propertyKeys) { + value = [propertyKey valueInObject:value]; + } + if (value) break; + } + + // 值的过滤 + id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property]; + if (newValue != value) { // 有过滤后的新值 + [property setValue:newValue forObject:self]; + return; + } + + // 如果没有值,就直接返回 + if (!value || value == [NSNull null]) return; + + // 2.复杂处理 + MJPropertyType *type = property.type; + Class propertyClass = type.typeClass; + Class objectClass = [property objectClassInArrayForClass:[self class]]; + + // 不可变 -> 可变处理 + if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) { + value = [NSMutableArray arrayWithArray:value]; + } else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) { + value = [NSMutableDictionary dictionaryWithDictionary:value]; + } else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) { + value = [NSMutableString stringWithString:value]; + } else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) { + value = [NSMutableData dataWithData:value]; + } + + if (!type.isFromFoundation && propertyClass) { // 模型属性 + value = [propertyClass mj_objectWithKeyValues:value context:context]; + } else if (objectClass) { + if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) { + // string array -> url array + NSMutableArray *urlArray = [NSMutableArray array]; + for (NSString *string in value) { + if (![string isKindOfClass:[NSString class]]) continue; + [urlArray addObject:string.mj_url]; + } + value = urlArray; + } else { // 字典数组-->模型数组 + value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context]; + } + } else if (propertyClass == [NSString class]) { + if ([value isKindOfClass:[NSNumber class]]) { + // NSNumber -> NSString + value = [value description]; + } else if ([value isKindOfClass:[NSURL class]]) { + // NSURL -> NSString + value = [value absoluteString]; + } + } else if ([value isKindOfClass:[NSString class]]) { + if (propertyClass == [NSURL class]) { + // NSString -> NSURL + // 字符串转码 + value = [value mj_url]; + } else if (type.isNumberType) { + NSString *oldValue = value; + + // NSString -> NSDecimalNumber, 使用 DecimalNumber 来转换数字, 避免丢失精度以及溢出 + NSDecimalNumber *decimalValue = [NSDecimalNumber decimalNumberWithString:oldValue + locale:numberLocale]; + + // 检查特殊情况 + if (decimalValue == NSDecimalNumber.notANumber) { + value = @(0); + }else if (propertyClass != [NSDecimalNumber class]) { + value = [decimalValue mj_standardValueWithTypeCode:type.code]; + } else { + value = decimalValue; + } + + // 如果是BOOL + if (type.isBoolType) { + // 字符串转BOOL(字符串没有charValue方法) + // 系统会调用字符串的charValue转为BOOL类型 + NSString *lower = [oldValue lowercaseString]; + if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) { + value = @YES; + } else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) { + value = @NO; + } + } + } + } else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){ + // 过滤 NSDecimalNumber类型 + if (![value isKindOfClass:[NSDecimalNumber class]]) { + value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]]; + } + } + + // 经过转换后, 最终检查 value 与 property 是否匹配 + if (propertyClass && ![value isKindOfClass:propertyClass]) { + value = nil; + } + + // 3.赋值 + [property setValue:value forObject:self]; + } @catch (NSException *exception) { + MJExtensionBuildError([self class], exception.reason); + MJExtensionLog(@"%@", exception); +#ifdef DEBUG + [exception raise]; +#endif + } + }]; + + // 转换完毕 + if ([self respondsToSelector:@selector(mj_didConvertToObjectWithKeyValues:)]) { + [self mj_didConvertToObjectWithKeyValues:keyValues]; + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored"-Wdeprecated-declarations" + if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) { + [self mj_keyValuesDidFinishConvertingToObject]; + } + if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]) { + [self mj_keyValuesDidFinishConvertingToObject:keyValues]; + } +#pragma clang diagnostic pop + return self; +} + ++ (instancetype)mj_objectWithKeyValues:(id)keyValues +{ + return [self mj_objectWithKeyValues:keyValues context:nil]; +} + ++ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context +{ + // 获得JSON对象 + keyValues = [keyValues mj_JSONObject]; + MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues参数不是一个字典"); + + if ([self isSubclassOfClass:[NSManagedObject class]] && context) { + NSString *entityName = [(NSManagedObject *)self entity].name; + return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context]; + } + return [[[self alloc] init] mj_setKeyValues:keyValues]; +} + ++ (instancetype)mj_objectWithFilename:(NSString *)filename +{ + MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil"); + + return [self mj_objectWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]]; +} + ++ (instancetype)mj_objectWithFile:(NSString *)file +{ + MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil"); + + return [self mj_objectWithKeyValues:[NSDictionary dictionaryWithContentsOfFile:file]]; +} + +#pragma mark - 字典数组 -> 模型数组 ++ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(NSArray *)keyValuesArray +{ + return [self mj_objectArrayWithKeyValuesArray:keyValuesArray context:nil]; +} + ++ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context +{ + // 如果是JSON字符串 + keyValuesArray = [keyValuesArray mj_JSONObject]; + + // 1.判断真实性 + MJExtensionAssertError([keyValuesArray isKindOfClass:[NSArray class]], nil, [self class], @"keyValuesArray参数不是一个数组"); + + // 如果数组里面放的是NSString、NSNumber等数据 + if ([MJFoundation isClassFromFoundation:self]) return [NSMutableArray arrayWithArray:keyValuesArray]; + + + // 2.创建数组 + NSMutableArray *modelArray = [NSMutableArray array]; + + // 3.遍历 + for (NSDictionary *keyValues in keyValuesArray) { + if ([keyValues isKindOfClass:[NSArray class]]){ + [modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]]; + } else { + id model = [self mj_objectWithKeyValues:keyValues context:context]; + if (model) [modelArray addObject:model]; + } + } + + return modelArray; +} + ++ (NSMutableArray *)mj_objectArrayWithFilename:(NSString *)filename +{ + MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil"); + + return [self mj_objectArrayWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]]; +} + ++ (NSMutableArray *)mj_objectArrayWithFile:(NSString *)file +{ + MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil"); + + return [self mj_objectArrayWithKeyValuesArray:[NSArray arrayWithContentsOfFile:file]]; +} + +#pragma mark - 模型 -> 字典 +- (NSMutableDictionary *)mj_keyValues +{ + return [self mj_keyValuesWithKeys:nil ignoredKeys:nil]; +} + +- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys +{ + return [self mj_keyValuesWithKeys:keys ignoredKeys:nil]; +} + +- (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys +{ + return [self mj_keyValuesWithKeys:nil ignoredKeys:ignoredKeys]; +} + +- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys +{ + // 如果自己不是模型类, 那就返回自己 + // 模型类过滤掉 NSNull + // 唯一一个不返回自己的 + if ([self isMemberOfClass:NSNull.class]) { return nil; } + // 这里虽然返回了自己, 但是其实是有报错信息的. + // TODO: 报错机制不好, 需要重做 + MJExtensionAssertError(![MJFoundation isClassFromFoundation:[self class]], (NSMutableDictionary *)self, [self class], @"不是自定义的模型类") + + id keyValues = [NSMutableDictionary dictionary]; + + Class clazz = [self class]; + NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames]; + NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames]; + + [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) { + @try { + // 0.检测是否被忽略 + if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return; + if ([ignoredPropertyNames containsObject:property.name]) return; + if (keys.count && ![keys containsObject:property.name]) return; + if ([ignoredKeys containsObject:property.name]) return; + + // 1.取出属性值 + id value = [property valueForObject:self]; + if (!value) return; + + // 2.如果是模型属性 + MJPropertyType *type = property.type; + Class propertyClass = type.typeClass; + if (!type.isFromFoundation && propertyClass) { + value = [value mj_keyValues]; + } else if ([value isKindOfClass:[NSArray class]]) { + // 3.处理数组里面有模型的情况 + value = [NSObject mj_keyValuesArrayWithObjectArray:value]; + } else if (propertyClass == [NSURL class]) { + value = [value absoluteString]; + } + + // 4.赋值 + if ([clazz mj_isReferenceReplacedKeyWhenCreatingKeyValues]) { + NSArray *propertyKeys = [[property propertyKeysForClass:clazz] firstObject]; + NSUInteger keyCount = propertyKeys.count; + // 创建字典 + __block id innerContainer = keyValues; + [propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) { + // 下一个属性 + MJPropertyKey *nextPropertyKey = nil; + if (idx != keyCount - 1) { + nextPropertyKey = propertyKeys[idx + 1]; + } + + if (nextPropertyKey) { // 不是最后一个key + // 当前propertyKey对应的字典或者数组 + id tempInnerContainer = [propertyKey valueInObject:innerContainer]; + if (tempInnerContainer == nil || [tempInnerContainer isKindOfClass:[NSNull class]]) { + if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) { + tempInnerContainer = [NSMutableDictionary dictionary]; + } else { + tempInnerContainer = [NSMutableArray array]; + } + if (propertyKey.type == MJPropertyKeyTypeDictionary) { + innerContainer[propertyKey.name] = tempInnerContainer; + } else { + innerContainer[propertyKey.name.intValue] = tempInnerContainer; + } + } + + if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) { + NSMutableArray *tempInnerContainerArray = tempInnerContainer; + int index = nextPropertyKey.name.intValue; + while (tempInnerContainerArray.count < index + 1) { + [tempInnerContainerArray addObject:[NSNull null]]; + } + } + + innerContainer = tempInnerContainer; + } else { // 最后一个key + if (propertyKey.type == MJPropertyKeyTypeDictionary) { + innerContainer[propertyKey.name] = value; + } else { + innerContainer[propertyKey.name.intValue] = value; + } + } + }]; + } else { + keyValues[property.name] = value; + } + } @catch (NSException *exception) { + MJExtensionBuildError([self class], exception.reason); + MJExtensionLog(@"%@", exception); +#ifdef DEBUG + [exception raise]; +#endif + } + }]; + + // 转换完毕 + if ([self respondsToSelector:@selector(mj_objectDidConvertToKeyValues:)]) { + [self mj_objectDidConvertToKeyValues:keyValues]; + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored"-Wdeprecated-declarations" + if ([self respondsToSelector:@selector(mj_objectDidFinishConvertingToKeyValues)]) { + [self mj_objectDidFinishConvertingToKeyValues]; + } +#pragma clang diagnostic pop + + return keyValues; +} +#pragma mark - 模型数组 -> 字典数组 ++ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray +{ + return [self mj_keyValuesArrayWithObjectArray:objectArray keys:nil ignoredKeys:nil]; +} + ++ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys +{ + return [self mj_keyValuesArrayWithObjectArray:objectArray keys:keys ignoredKeys:nil]; +} + ++ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray ignoredKeys:(NSArray *)ignoredKeys +{ + return [self mj_keyValuesArrayWithObjectArray:objectArray keys:nil ignoredKeys:ignoredKeys]; +} + ++ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys +{ + // 0.判断真实性 + MJExtensionAssertError([objectArray isKindOfClass:[NSArray class]], nil, [self class], @"objectArray参数不是一个数组"); + + // 1.创建数组 + NSMutableArray *keyValuesArray = [NSMutableArray array]; + for (id object in objectArray) { + if (keys) { + id convertedObj = [object mj_keyValuesWithKeys:keys]; + if (!convertedObj) { continue; } + [keyValuesArray addObject:convertedObj]; + } else { + id convertedObj = [object mj_keyValuesWithIgnoredKeys:ignoredKeys]; + if (!convertedObj) { continue; } + [keyValuesArray addObject:convertedObj]; + } + } + return keyValuesArray; +} + +#pragma mark - 转换为JSON +- (NSData *)mj_JSONData +{ + if ([self isKindOfClass:[NSString class]]) { + return [((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding]; + } else if ([self isKindOfClass:[NSData class]]) { + return (NSData *)self; + } + + return [NSJSONSerialization dataWithJSONObject:[self mj_JSONObject] options:kNilOptions error:nil]; +} + +- (id)mj_JSONObject +{ + if ([self isKindOfClass:[NSString class]]) { + return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil]; + } else if ([self isKindOfClass:[NSData class]]) { + return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil]; + } + + return self.mj_keyValues; +} + +- (NSString *)mj_JSONString +{ + if ([self isKindOfClass:[NSString class]]) { + return (NSString *)self; + } else if ([self isKindOfClass:[NSData class]]) { + return [[NSString alloc] initWithData:(NSData *)self encoding:NSUTF8StringEncoding]; + } + + return [[NSString alloc] initWithData:[self mj_JSONData] encoding:NSUTF8StringEncoding]; +} + +@end diff --git a/iosApp/iosApp/Libs/MJExtension/NSObject+MJProperty.h b/iosApp/iosApp/Libs/MJExtension/NSObject+MJProperty.h new file mode 100644 index 0000000..1bf88e9 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSObject+MJProperty.h @@ -0,0 +1,70 @@ +// +// NSObject+MJProperty.h +// MJExtensionExample +// +// Created by MJ Lee on 15/4/17. +// Copyright (c) 2015年 小码哥. All rights reserved. +// + +#import +#import "MJExtensionConst.h" + +@class MJProperty; + +/** + * 遍历成员变量用的block + * + * @param property 成员的包装对象 + * @param stop YES代表停止遍历,NO代表继续遍历 + */ +typedef void (^MJPropertiesEnumeration)(MJProperty *property, BOOL *stop); + +/** 将属性名换为其他key去字典中取值 */ +typedef NSDictionary * (^MJReplacedKeyFromPropertyName)(void); +typedef id (^MJReplacedKeyFromPropertyName121)(NSString *propertyName); +/** 数组中需要转换的模型类 */ +typedef NSDictionary * (^MJObjectClassInArray)(void); +/** 用于过滤字典中的值 */ +typedef id (^MJNewValueFromOldValue)(id object, id oldValue, MJProperty *property); + +/** + * 成员属性相关的扩展 + */ +@interface NSObject (MJProperty) +#pragma mark - 遍历 +/** + * 遍历所有的成员 + */ ++ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration; + +#pragma mark - 新值配置 +/** + * 用于过滤字典中的值 + * + * @param newValueFormOldValue 用于过滤字典中的值 + */ ++ (void)mj_setupNewValueFromOldValue:(MJNewValueFromOldValue)newValueFormOldValue; ++ (id)mj_getNewValueFromObject:(__unsafe_unretained id)object oldValue:(__unsafe_unretained id)oldValue property:(__unsafe_unretained MJProperty *)property; + +#pragma mark - key配置 +/** + * 将属性名换为其他key去字典中取值 + * + * @param replacedKeyFromPropertyName 将属性名换为其他key去字典中取值 + */ ++ (void)mj_setupReplacedKeyFromPropertyName:(MJReplacedKeyFromPropertyName)replacedKeyFromPropertyName; +/** + * 将属性名换为其他key去字典中取值 + * + * @param replacedKeyFromPropertyName121 将属性名换为其他key去字典中取值 + */ ++ (void)mj_setupReplacedKeyFromPropertyName121:(MJReplacedKeyFromPropertyName121)replacedKeyFromPropertyName121; + +#pragma mark - array model class配置 +/** + * 数组中需要转换的模型类 + * + * @param objectClassInArray 数组中需要转换的模型类 + */ ++ (void)mj_setupObjectClassInArray:(MJObjectClassInArray)objectClassInArray; +@end diff --git a/iosApp/iosApp/Libs/MJExtension/NSObject+MJProperty.m b/iosApp/iosApp/Libs/MJExtension/NSObject+MJProperty.m new file mode 100644 index 0000000..71b08f4 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSObject+MJProperty.m @@ -0,0 +1,240 @@ +// +// NSObject+MJProperty.m +// MJExtensionExample +// +// Created by MJ Lee on 15/4/17. +// Copyright (c) 2015年 小码哥. All rights reserved. +// + +#import "NSObject+MJProperty.h" +#import "NSObject+MJKeyValue.h" +#import "NSObject+MJCoding.h" +#import "NSObject+MJClass.h" +#import "MJProperty.h" +#import "MJFoundation.h" +#import + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + +static const char MJReplacedKeyFromPropertyNameKey = '\0'; +static const char MJReplacedKeyFromPropertyName121Key = '\0'; +static const char MJNewValueFromOldValueKey = '\0'; +static const char MJObjectClassInArrayKey = '\0'; + +static const char MJCachedPropertiesKey = '\0'; + +dispatch_semaphore_t mje_signalSemaphore; +dispatch_once_t mje_onceTokenSemaphore; + +@implementation NSObject (Property) + ++ (NSMutableDictionary *)mj_propertyDictForKey:(const void *)key +{ + static NSMutableDictionary *replacedKeyFromPropertyNameDict; + static NSMutableDictionary *replacedKeyFromPropertyName121Dict; + static NSMutableDictionary *newValueFromOldValueDict; + static NSMutableDictionary *objectClassInArrayDict; + static NSMutableDictionary *cachedPropertiesDict; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + replacedKeyFromPropertyNameDict = [NSMutableDictionary dictionary]; + replacedKeyFromPropertyName121Dict = [NSMutableDictionary dictionary]; + newValueFromOldValueDict = [NSMutableDictionary dictionary]; + objectClassInArrayDict = [NSMutableDictionary dictionary]; + cachedPropertiesDict = [NSMutableDictionary dictionary]; + }); + + if (key == &MJReplacedKeyFromPropertyNameKey) return replacedKeyFromPropertyNameDict; + if (key == &MJReplacedKeyFromPropertyName121Key) return replacedKeyFromPropertyName121Dict; + if (key == &MJNewValueFromOldValueKey) return newValueFromOldValueDict; + if (key == &MJObjectClassInArrayKey) return objectClassInArrayDict; + if (key == &MJCachedPropertiesKey) return cachedPropertiesDict; + return nil; +} + +#pragma mark - --私有方法-- ++ (id)mj_propertyKey:(NSString *)propertyName +{ + MJExtensionAssertParamNotNil2(propertyName, nil); + + __block id key = nil; + // 查看有没有需要替换的key + if ([self respondsToSelector:@selector(mj_replacedKeyFromPropertyName121:)]) { + key = [self mj_replacedKeyFromPropertyName121:propertyName]; + } + + // 调用block + if (!key) { + [self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) { + MJReplacedKeyFromPropertyName121 block = objc_getAssociatedObject(c, &MJReplacedKeyFromPropertyName121Key); + if (block) { + key = block(propertyName); + } + if (key) *stop = YES; + }]; + } + + // 查看有没有需要替换的key + if ((!key || [key isEqual:propertyName]) && [self respondsToSelector:@selector(mj_replacedKeyFromPropertyName)]) { + key = [self mj_replacedKeyFromPropertyName][propertyName]; + } + + if (!key || [key isEqual:propertyName]) { + [self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) { + NSDictionary *dict = objc_getAssociatedObject(c, &MJReplacedKeyFromPropertyNameKey); + if (dict) { + key = dict[propertyName]; + } + if (key && ![key isEqual:propertyName]) *stop = YES; + }]; + } + + // 2.用属性名作为key + if (!key) key = propertyName; + + return key; +} + ++ (Class)mj_propertyObjectClassInArray:(NSString *)propertyName +{ + __block id clazz = nil; + if ([self respondsToSelector:@selector(mj_objectClassInArray)]) { + clazz = [self mj_objectClassInArray][propertyName]; + } + + if (!clazz) { + [self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) { + NSDictionary *dict = objc_getAssociatedObject(c, &MJObjectClassInArrayKey); + if (dict) { + clazz = dict[propertyName]; + } + if (clazz) *stop = YES; + }]; + } + + // 如果是NSString类型 + if ([clazz isKindOfClass:[NSString class]]) { + clazz = NSClassFromString(clazz); + } + return clazz; +} + +#pragma mark - --公共方法-- ++ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration +{ + // 获得成员变量 + NSArray *cachedProperties = [self mj_properties]; + // 遍历成员变量 + BOOL stop = NO; + for (MJProperty *property in cachedProperties) { + enumeration(property, &stop); + if (stop) break; + } +} + +#pragma mark - 公共方法 ++ (NSArray *)mj_properties +{ + MJExtensionSemaphoreCreate + MJ_LOCK(mje_signalSemaphore); + NSMutableDictionary *cachedInfo = [self mj_propertyDictForKey:&MJCachedPropertiesKey]; + NSMutableArray *cachedProperties = cachedInfo[NSStringFromClass(self)]; + if (cachedProperties == nil) { + cachedProperties = [NSMutableArray array]; + + [self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) { + // 1.获得所有的成员变量 + unsigned int outCount = 0; + objc_property_t *properties = class_copyPropertyList(c, &outCount); + + // 2.遍历每一个成员变量 + for (unsigned int i = 0; i +#import "MJExtensionConst.h" + +@interface NSString (MJExtension) +/** + * 驼峰转下划线(loveYou -> love_you) + */ +- (NSString *)mj_underlineFromCamel; +/** + * 下划线转驼峰(love_you -> loveYou) + */ +- (NSString *)mj_camelFromUnderline; +/** + * 首字母变大写 + */ +- (NSString *)mj_firstCharUpper; +/** + * 首字母变小写 + */ +- (NSString *)mj_firstCharLower; + +- (BOOL)mj_isPureInt; + +- (NSURL *)mj_url; +@end diff --git a/iosApp/iosApp/Libs/MJExtension/NSString+MJExtension.m b/iosApp/iosApp/Libs/MJExtension/NSString+MJExtension.m new file mode 100644 index 0000000..20533c5 --- /dev/null +++ b/iosApp/iosApp/Libs/MJExtension/NSString+MJExtension.m @@ -0,0 +1,80 @@ +// +// NSString+MJExtension.m +// MJExtensionExample +// +// Created by MJ Lee on 15/6/7. +// Copyright (c) 2015年 小码哥. All rights reserved. +// + +#import "NSString+MJExtension.h" + +@implementation NSString (MJExtension) +- (NSString *)mj_underlineFromCamel +{ + if (self.length == 0) return self; + NSMutableString *string = [NSMutableString string]; + for (NSUInteger i = 0; i= 2) [string appendString:[cmp substringFromIndex:1]]; + } else { + [string appendString:cmp]; + } + } + return string; +} + +- (NSString *)mj_firstCharLower +{ + if (self.length == 0) return self; + NSMutableString *string = [NSMutableString string]; + [string appendString:[NSString stringWithFormat:@"%c", [self characterAtIndex:0]].lowercaseString]; + if (self.length >= 2) [string appendString:[self substringFromIndex:1]]; + return string; +} + +- (NSString *)mj_firstCharUpper +{ + if (self.length == 0) return self; + NSMutableString *string = [NSMutableString string]; + [string appendString:[NSString stringWithFormat:@"%c", [self characterAtIndex:0]].uppercaseString]; + if (self.length >= 2) [string appendString:[self substringFromIndex:1]]; + return string; +} + +- (BOOL)mj_isPureInt +{ + NSScanner *scan = [NSScanner scannerWithString:self]; + int val; + return [scan scanInt:&val] && [scan isAtEnd]; +} + +- (NSURL *)mj_url +{ +// [self stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"!$&'()*+,-./:;=?@_~%#[]"]]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored"-Wdeprecated-declarations" + return [NSURL URLWithString:(NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", NULL,kCFStringEncodingUTF8))]; +#pragma clang diagnostic pop +} +@end