├── .travis.yml ├── Util.h ├── .gitignore ├── Util.m ├── CoreFoundation ├── Other │ ├── NSURL+ParameterParsing.h │ └── NSURL+ParameterParsing.m ├── NSString │ ├── NSString+StringAdditions.h │ └── NSString+StringAdditions.m ├── NSArray │ ├── FPTuple.m │ ├── FPTuple.h │ ├── NSArray+FPAdditions.m │ └── NSArray+FPAdditions.h ├── NSObject │ ├── NSObject+PerformBlock.h │ ├── NSObject+KVCExtensions.h │ ├── NSObject+ObserveActions.h │ ├── NSObject+PerformBlock.m │ ├── NSObject+KVCExtensions.m │ └── NSObject+ObserveActions.m └── Date+Time │ ├── TimeHelpers.h │ ├── NSDate+Additions.h │ ├── TimeHelpers.m │ └── NSDate+Additions.m ├── objc-utils.podspec ├── LICENSE └── README.mdown /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | rvm: 1.9.3 4 | 5 | script: pod lib lint 6 | -------------------------------------------------------------------------------- /Util.h: -------------------------------------------------------------------------------- 1 | // 2 | // Util.h 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 13.06.11. 6 | // Copyright 2011 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | id fromNil(id origValue, id alternativeValue); 13 | NSString *fromEmptyStr(NSString *origValue, NSString *alternativeValue); 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | #Emacs and vim backup and lock files 4 | *~ 5 | .\#* 6 | .*.swp 7 | 8 | .DS_Store 9 | 10 | *.swp 11 | *~.nib 12 | 13 | **/build/* 14 | build/* 15 | 16 | xcuserdata/* 17 | **/xcuserdata/* 18 | 19 | *.pbxuser 20 | *.perspective 21 | *.perspectivev? 22 | 23 | *.mode?v? 24 | 25 | **.docset/* 26 | 27 | _darcs/* 28 | **/_darcs/* 29 | 30 | doc/* 31 | 32 | *.svn* 33 | -------------------------------------------------------------------------------- /Util.m: -------------------------------------------------------------------------------- 1 | // 2 | // Util.m 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 13.06.11. 6 | // Copyright 2011 Dustlab. All rights reserved. 7 | // 8 | 9 | #import "Util.h" 10 | 11 | id fromNil(id origValue, id alternativeValue) { 12 | if(origValue == nil || origValue == [NSNull null]) { 13 | return alternativeValue; 14 | } 15 | return origValue; 16 | } 17 | 18 | NSString *fromEmptyStr(NSString *origValue, NSString *alternativeValue) { 19 | if(origValue == nil || [origValue isEqualToString:@""]) { 20 | return alternativeValue; 21 | } 22 | return origValue; 23 | } 24 | -------------------------------------------------------------------------------- /CoreFoundation/Other/NSURL+ParameterParsing.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+ParameterParsing.h 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface NSURL (ParameterParsing) 13 | 14 | /** 15 | Get the parameters that were specified in the URL's query 16 | as a &-separated list of "key=value" pairs. 17 | The behaviour if there is more than one = (i.e. not with a & in between) in the string is undefined. 18 | */ 19 | - (NSDictionary *)getParameters; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /CoreFoundation/NSString/NSString+StringAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+StringAdditions.h 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 30.12.11. 6 | // Copyright (c) 2011 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (StringAdditions) 12 | 13 | - (NSString *)reverseString; 14 | 15 | - (NSString *)commonSuffixWithString:(NSString *)aString options:(NSStringCompareOptions)mask; 16 | 17 | /** 18 | * Like -[capitalizedString], but capitalizes only the first word. 19 | */ 20 | - (NSString *)startCapitalizedString; 21 | 22 | @end 23 | 24 | -------------------------------------------------------------------------------- /CoreFoundation/NSArray/FPTuple.m: -------------------------------------------------------------------------------- 1 | // 2 | // FPTuple.m 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import "FPTuple.h" 10 | 11 | 12 | @implementation FPTuple 13 | @synthesize fst, snd; 14 | 15 | - (id)initWithFirst:(id)first second:(id)second { 16 | if((self = [super init])) { 17 | self.fst = first; 18 | self.snd = second; 19 | } 20 | return self; 21 | } 22 | 23 | + (id)tupleWithFirst:(id)first second:(id)second { 24 | return [[FPTuple alloc] initWithFirst:first second:second]; 25 | } 26 | 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /CoreFoundation/NSObject/NSObject+PerformBlock.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+PerformBlock.h 3 | // CoinToss 4 | // 5 | // Created by Ludwig Schubert on 23.05.12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSObject (PerformBlock) 12 | 13 | - (void)performBlock:(void(^)(void))block afterDelay:(NSTimeInterval)delay __attribute__((deprecated)); 14 | 15 | - (void)performBlockOnMainThread:(void(^)(void))block afterDelay:(NSTimeInterval)delay; 16 | 17 | - (void)performBlockInBackground:(void(^)(void))block afterDelay:(NSTimeInterval)delay; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /CoreFoundation/NSObject/NSObject+KVCExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCExtensions.h 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface NSObject (KVCExtensions) 13 | 14 | /** 15 | Similar to -[setValuesForKeysWithDictionary:], but uses keyPaths instead of keys. 16 | */ 17 | - (void)setValuesForKeyPathsWithDictionary:(NSDictionary *)keyedValues; 18 | 19 | /** 20 | Similar to -[dictionaryWithValuesForKeys:], but uses keyPaths instead of keys. 21 | */ 22 | - (NSDictionary *)dictionaryWithValuesForKeyPaths:(NSArray *)keyPaths; 23 | @end 24 | -------------------------------------------------------------------------------- /CoreFoundation/NSArray/FPTuple.h: -------------------------------------------------------------------------------- 1 | // 2 | // FPTuple.h 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | A tuple containing two arbitrary objects. 13 | This class is here to keep the user from having to write an extra class (or having to use a NSDictionary) when all we want is just a cheap wrapper. 14 | */ 15 | @interface FPTuple : NSObject { 16 | id fst; 17 | id snd; 18 | } 19 | 20 | @property (nonatomic, strong) id fst; 21 | @property (nonatomic, strong) id snd; 22 | 23 | - (id)initWithFirst:(id)fst second:(id)snd; 24 | + (id)tupleWithFirst:(id)fst second:(id)snd; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /CoreFoundation/Date+Time/TimeHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimeHelpers.h 3 | // Checklists 4 | // 5 | // Created by Marcel Ruegenberg on 06.02.11. 6 | // Copyright 2011 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | Get the absolute length of a time interval as a string. 13 | Like timeIntervalToString, but without the "before" and "after" suffix. 14 | */ 15 | NSString *timeLengthToString(NSInteger timeLength); 16 | 17 | /** 18 | Get the absolute length of a time interval as a string. 19 | Explicitly spells out only the number of days, the rest is abbreviated as hh:mm. 20 | */ 21 | NSString *timeLengthToShortString(NSInteger timeLength); 22 | 23 | /** 24 | Convert a time interval (in seconds) to a string of the form "5 days, 3 hours, 2 minutes before" 25 | */ 26 | NSString *timeIntervalToString(NSInteger timeInterval); 27 | -------------------------------------------------------------------------------- /CoreFoundation/Other/NSURL+ParameterParsing.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+ParameterParsing.m 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import "NSURL+ParameterParsing.h" 10 | 11 | 12 | @implementation NSURL (ParameterParsing) 13 | 14 | - (NSDictionary *)getParameters { 15 | NSArray *pairs = [[self query] componentsSeparatedByString:@"&"]; 16 | NSMutableDictionary *result = [NSMutableDictionary dictionary]; 17 | for(NSString *pair in pairs) { 18 | NSArray *keyValue = [pair componentsSeparatedByString:@"="]; 19 | if([keyValue count] > 1) 20 | [result setValue:[keyValue objectAtIndex:1] 21 | forKey:[keyValue objectAtIndex:0]]; 22 | else if([keyValue count] > 0) 23 | [result setValue:@"" forKey:[keyValue objectAtIndex:0]]; 24 | } 25 | 26 | return result; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /objc-utils.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "objc-utils" 3 | s.version = "0.6.1" 4 | s.summary = "Utilities for developing with Objective-C, particularly under iOS." 5 | s.description = <<-DESC 6 | This is a collection of useful classes and extensions for common classes in CoreFoundation and UIKit. 7 | 8 | * Various categories to make the standard CoreFoundation and UIKit classes more useful. 9 | * A few helper classes with the same purpose. 10 | DESC 11 | s.homepage = "https://github.com/mruegenberg/objc-utils" 12 | s.license = { :type => 'MIT', :file => 'LICENSE' } 13 | s.author = { "Marcel Ruegenberg" => "github@dustlab.com" } 14 | s.source = { :git => "https://github.com/mruegenberg/objc-utils.git", :tag => s.version } 15 | 16 | s.platform = :ios, "5.0" 17 | s.requires_arc = true 18 | 19 | s.source_files = 'Util.h', 'Util.m', 'CoreFoundation/**/*.{h,m,c}' 20 | s.library = 'z' 21 | s.frameworks = 'Foundation' 22 | end 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Marcel Ruegenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /CoreFoundation/NSObject/NSObject+ObserveActions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+ObserveActions.h 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 18.06.11. 6 | // Copyright 2011 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | typedef void(^ObserveAction)(void); 13 | 14 | /** 15 | Allows for a specific action (in the form of a block) to be executed when a specific value of an object changes. 16 | */ 17 | 18 | @interface NSObject (ObserveActions) 19 | 20 | // add an observer action for a keypath. if an observer with the same context already exists, that observer is overwritten. 21 | // usually pass the object from which you call the method as a reference both when adding and removing the observer. 22 | // 23 | // you must call -[removeActionObserverForKeyPath:context:] before the object is deallocated. 24 | - (void)addObserverAction:(ObserveAction)action forKeyPath:(NSString *)keyPath context:(id)context; 25 | 26 | // remove the action observer for the corresponding keypath with a specific context (which may be nil) 27 | - (void)removeActionObserverForKeyPath:(NSString *)keyPath context:(id)context; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /CoreFoundation/NSString/NSString+StringAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+StringAdditions.m 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 30.12.11. 6 | // Copyright (c) 2011 Dustlab. All rights reserved. 7 | // 8 | 9 | #import "NSString+StringAdditions.h" 10 | 11 | @implementation NSString (StringAdditions) 12 | 13 | - (NSString *)reverseString { 14 | NSUInteger len = [self length]; 15 | NSMutableString *rtr=[NSMutableString stringWithCapacity:len]; 16 | 17 | while (len > (NSUInteger)0) { 18 | unichar uch = [self characterAtIndex:--len]; 19 | [rtr appendString:[NSString stringWithCharacters:&uch length:1]]; 20 | } 21 | return rtr; 22 | } 23 | 24 | - (NSString *)commonSuffixWithString:(NSString *)aString options:(NSStringCompareOptions)mask { 25 | if(aString == nil) return self; 26 | return [[[self reverseString] commonPrefixWithString:[aString reverseString] options:mask] reverseString]; 27 | } 28 | 29 | - (NSString *)startCapitalizedString { 30 | return [self stringByReplacingCharactersInRange:NSMakeRange(0,1) 31 | withString:[[self substringToIndex:1] capitalizedString]]; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /CoreFoundation/NSObject/NSObject+PerformBlock.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+PerformBlock.m 3 | // CoinToss 4 | // 5 | // Created by Ludwig Schubert on 23.05.12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "NSObject+PerformBlock.h" 10 | 11 | @implementation NSObject (PerformBlock) 12 | 13 | - (void)performBlock:(void(^)(void))block afterDelay:(NSTimeInterval)delay 14 | { 15 | #pragma clang diagnostic push 16 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 17 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), 18 | dispatch_get_current_queue(), ^{ 19 | block(); 20 | }); 21 | #pragma clang diagnostic pop 22 | } 23 | 24 | - (void)performBlockOnMainThread:(void(^)(void))block afterDelay:(NSTimeInterval)delay 25 | { 26 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), 27 | dispatch_get_main_queue(), ^{ 28 | block(); 29 | }); 30 | } 31 | 32 | 33 | - (void)performBlockInBackground:(void(^)(void))block afterDelay:(NSTimeInterval)delay 34 | { 35 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), 36 | dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 37 | block(); 38 | }); 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /CoreFoundation/NSArray/NSArray+FPAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // Array+FPAdditions.m 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import "NSArray+FPAdditions.h" 10 | #import "FPTuple.h" 11 | 12 | 13 | @implementation NSArray (ArrayFP) 14 | 15 | - (NSArray *)mapWithBlock:(id (^)(id obj))block { 16 | NSMutableArray *result = [[NSMutableArray alloc] init]; 17 | for(id val in self) { 18 | id mappedVal = block(val); 19 | if(mappedVal) 20 | [result addObject:mappedVal]; 21 | } 22 | return result; 23 | } 24 | 25 | - (NSArray *)zipWithArray:(NSArray *)otherArray { 26 | NSUInteger cnt = [self count]; 27 | if(otherArray == nil) cnt = 0; 28 | else if([otherArray count] < cnt) cnt = [otherArray count]; 29 | 30 | NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:cnt]; 31 | NSUInteger i; 32 | for (i = 0; i < cnt; i++) { 33 | NSObject * obj1 = [self objectAtIndex:i]; 34 | NSObject * obj2 = [otherArray objectAtIndex:i]; 35 | FPTuple *zipped = [FPTuple tupleWithFirst:obj1 second:obj2]; 36 | [result addObject:zipped]; 37 | } 38 | 39 | return result; 40 | } 41 | 42 | - (id)firstObject { 43 | if([self count] == 0) 44 | return nil; 45 | else 46 | return [self objectAtIndex:0]; 47 | } 48 | 49 | - (NSArray *)initialArray { 50 | NSUInteger c = [self count]; 51 | if(c == 0) return self; 52 | else return [self subarrayWithRange:NSMakeRange(0, c - 1)]; 53 | } 54 | 55 | @end 56 | 57 | -------------------------------------------------------------------------------- /CoreFoundation/NSObject/NSObject+KVCExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCExtensions.m 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import "NSObject+KVCExtensions.h" 10 | #import "Util.h" 11 | 12 | @implementation NSObject (KVCExtensions) 13 | 14 | - (void)setValuesForKeyPathsWithDictionary:(NSDictionary *)keyedValues { 15 | for(id keyPath in keyedValues) { 16 | NSArray *components = [keyPath componentsSeparatedByString:@"."]; 17 | NSArray *initialPathAry = [components subarrayWithRange:NSMakeRange(0, MAX(0, [components count] - 1))]; 18 | NSString *initialPath = [initialPathAry componentsJoinedByString:@"."]; 19 | NSString *path = initialPath; 20 | if(path == nil || [path isEqualToString:@""]) { 21 | path = keyPath; 22 | } 23 | if([keyedValues valueForKeyPath:keyPath] == [NSNull null]) { 24 | if([self valueForKeyPath:path] != nil) 25 | [self setValue:nil forKeyPath:keyPath]; 26 | else 27 | [self setValue:nil forKey:keyPath]; 28 | } 29 | else { 30 | if([self valueForKeyPath:path] != nil) 31 | [self setValue:[keyedValues objectForKey:keyPath] forKeyPath:keyPath]; 32 | else 33 | [self setValue:[keyedValues objectForKey:keyPath] forKey:keyPath]; 34 | } 35 | } 36 | } 37 | 38 | - (NSDictionary *)dictionaryWithValuesForKeyPaths:(NSArray *)keyPaths { 39 | NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; 40 | 41 | for(NSString *k in keyPaths) { 42 | if([self valueForKeyPath:k] == nil) 43 | [dict setValue:[NSNull null] forKey:k]; 44 | else 45 | [dict setValue:[self valueForKeyPath:k] forKey:k]; 46 | } 47 | 48 | return dict; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | ObjC-Utils 2 | ========== 3 | This is a collection of useful classes and extensions for common classes in `CoreFoundation` and `UIKit`. 4 | 5 | What is in here 6 | --------------- 7 | - Various categories to make the standard CoreFoundation and UIKit classes more useful 8 | - A few helper classes with the same purpose 9 | 10 | Compatibility 11 | ------------- 12 | - As of version 0.2.0, all code requires an ARC (Automatic Reference Counting) capable compiler. 13 | - Everything should be compatible with the iOS SDK 5 or above. Since version 0.5, OS X should be supported as well. 14 | 15 | How to use 16 | ---------- 17 | Just copy the files you need (and their dependencies) to your project. 18 | 19 | You can also use objc-utils with [CocoaPods](http://cocoapods.org). 20 | 21 | Replacements for removed categories 22 | ----------------------------------- 23 | Over time, some categories were removed, since their functionality is offered in separate projects: 24 | - Use [KSCrypto](https://github.com/karelia/KSCrypto) instead of `NSString+Hash`. Note, however, that the resulting hashes are different, as NSString+Hash used Base32 encoding to convert hashes to strings. 25 | - Use the [GZIP](https://github.com/rcdilorenzo/GZIP) instead of `NSData+CocoaDevUserAdditions`. GZIP, using the default compression/decompression methods is fully compatible to `NSData+CocoaDevUserAdditions`. 26 | - All UIKit-related functionality was moved to [uikit-utils](https://github.com/mruegenberg/uikit-utils) and [ios-versioncheck](https://github.com/mruegenberg/ios-versioncheck). 27 | 28 | License 29 | ------- 30 | MIT. 31 | (Before version 0.5.0, a custom permissive license was used.) 32 | 33 | ## Contact 34 | 35 | ![Travis CI build status](https://api.travis-ci.org/mruegenberg/objc-utils.png) 36 | 37 | Bug reports and pull requests are welcome! Contact me via e-mail or just by opening an issue on GitHub. 38 | -------------------------------------------------------------------------------- /CoreFoundation/NSArray/NSArray+FPAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Array+FPAdditions.h 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /** 13 | Helpers for more idiomatic programming with arrays. 14 | Inspired by functional programming languages. 15 | */ 16 | @interface NSArray (ArrayFP) 17 | 18 | /** 19 | Perform a block on each element of an array, and return an array of the results. 20 | If the block returns nil for an object, that object is not added to the block. This may change in the future to NSNull objects being added, you should not rely on this behavior. 21 | 22 | This method is somewhat similar to the makeObjectsPerformSelector: but uses a block and aggregates the return values. 23 | @param aSelector the selector to perform 24 | @return a new array with the results of performing the selector on each array element 25 | */ 26 | - (NSArray *)mapWithBlock:(id (^)(id obj))block; 27 | 28 | /** 29 | "Zips" two arrays (for more understanding, look up the word zipper in the dictionary). 30 | Each element of self is paired (as an `FPTuple`) with the corresponding element of otherArray. The length of the result is the length of the shorter of the two arrays. 31 | `[[a,b,c] zipWithArray:[1,2,3,4]]` results in `[(a,1),(b,2),(c,3)]`. 32 | @param otherArray Some other array with which to zip. If this is nil, it is treated as an empty array. 33 | @return The result of zipping the two arrays as an array of `FPTuple` objects. 34 | */ 35 | - (NSArray *)zipWithArray:(NSArray *)otherArray; 36 | 37 | /** 38 | The array's first object. Similar to -[lastObject]. 39 | Returns nil if the array is empty. 40 | */ 41 | - (id)firstObject; 42 | 43 | // array with all objects but the last; if the original array was empty, the original array is returned 44 | - (NSArray *)initialArray; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /CoreFoundation/Date+Time/NSDate+Additions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Additions.h 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /** 13 | Helpers for displaying dates. 14 | */ 15 | @interface NSDate (DatePrinters) 16 | 17 | /** 18 | Return a string representing the time component of the date. 19 | */ 20 | - (NSString *)shortTimeString; 21 | 22 | /** 23 | Return a string representing the date component of the date in 24 | (locale-dependent) short date form. 25 | */ 26 | - (NSString *)dateString; 27 | 28 | /** 29 | Return a string representing the full weekday component of the date in the current locale 30 | */ 31 | - (NSString *)weekdayString; 32 | 33 | /** 34 | Return a string representing the full weekday component of the date in the current locale 35 | */ 36 | - (NSString *)shortWeekdayString; 37 | 38 | 39 | /** 40 | Return a string representing the date and time component of the string in 41 | (locale-dependent) short date and short time form. 42 | */ 43 | - (NSString *)dateTimeString; 44 | 45 | 46 | /** 47 | Return a string representing the date and time component of the string in 48 | (locale-dependent) short date and short time form, with the weekday. 49 | */ 50 | - (NSString *)longDateTimeString; 51 | 52 | @end 53 | 54 | 55 | @interface NSDate (DateHelpers) 56 | - (NSDate *)nextHour; 57 | - (NSDate *)prevHour; 58 | - (NSDate *)dateByAddingDays:(NSInteger)days; 59 | - (NSDate *)dateByAddingMinutes:(NSInteger)minutes; 60 | - (NSDate *)prevDay; 61 | - (NSDate *)nextDay; 62 | - (NSDate *)prevWeek; 63 | - (NSDate *)nextWeek; 64 | /** 65 | Returns a date that represents the current time. 66 | */ 67 | //+ (NSDate *)nowTime; 68 | /** 69 | Returns a date that represents just the time of self. 70 | */ 71 | - (NSDate *)justTime; 72 | /** 73 | Returns a date that represents just the time of self with minutes and seconds set to zero. 74 | */ 75 | - (NSDate *)justHour; 76 | 77 | /** 78 | Returns a date that represents just the time of right now. 79 | */ 80 | + (NSDate *)nowTime; 81 | 82 | - (NSDate *)beginOfDay; 83 | 84 | - (NSDate *)endOfDay; 85 | 86 | - (NSDate *)middleOfDay; 87 | 88 | /** 89 | Returns a version of the date whose minutes component is rounded to the amount of minutes in the first argument. 90 | @param minutes The minutes to which to round. If zero, the minute part is discarded. 91 | */ 92 | - (NSDate *)dateRoundedToMinutes:(NSUInteger)minutes; 93 | 94 | + (NSDate *)dateWithHour:(NSUInteger)hour minutes:(NSUInteger)minutes seconds:(NSUInteger)seconds; 95 | 96 | - (NSInteger)daysSinceDate:(NSDate *)date; 97 | 98 | // a fast implementtion of daysSinceDate. Requires that self is already equal to [self beginOfDay]. 99 | - (NSInteger)daysSinceDateFast:(NSDate *)date; 100 | 101 | - (BOOL)isWeekend; 102 | 103 | - (BOOL)isEarlier:(NSDate *)other; 104 | - (BOOL)isLater:(NSDate *)other; 105 | @end 106 | 107 | 108 | @interface NSDate (Decomposition) 109 | 110 | @property (readonly) NSInteger hour; 111 | @property (readonly) NSInteger minute; 112 | @property (readonly) NSInteger day; 113 | @property (readonly) NSInteger month; 114 | @property (readonly) NSInteger year; 115 | 116 | @end 117 | 118 | -------------------------------------------------------------------------------- /CoreFoundation/NSObject/NSObject+ObserveActions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+ObserveActions.m 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 18.06.11. 6 | // Copyright 2011 Dustlab. All rights reserved. 7 | // 8 | 9 | #import "NSObject+ObserveActions.h" 10 | #import 11 | 12 | 13 | @interface ObserveActionBinding : NSObject 14 | 15 | // note: never set these directly, only set them with the custom init method 16 | @property (assign) NSObject *observedObj; 17 | @property (copy) NSString *keyPath; 18 | @property (copy) ObserveAction action; 19 | @property BOOL bindingActive; 20 | @property (retain) id context; 21 | 22 | - (id)initWithObservedObject:(NSObject *)observedObj keyPath:(NSString *)keyPath action:(ObserveAction)action context:(id)context; 23 | - (void)deactivateBinding; 24 | 25 | @end 26 | 27 | @implementation ObserveActionBinding 28 | @synthesize observedObj, keyPath, action, bindingActive, context; 29 | 30 | - (id)initWithObservedObject:(NSObject *)_observedObj keyPath:(NSString *)_keyPath action:(ObserveAction)_action context:(id)_context { 31 | if((self = [super init])) { 32 | self.observedObj = _observedObj; self.keyPath = _keyPath; self.action = _action; self.context = _context; 33 | [self.observedObj addObserver:self forKeyPath:self.keyPath options:0 context:(__bridge void *)(_context)]; // passing the context is not really necessary 34 | bindingActive = YES; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)observeValueForKeyPath:(NSString *)_keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)_context { 40 | NSAssert2([_keyPath isEqualToString:self.keyPath], @"Action binding called with invalid keyPath %@ instead of ", _keyPath, self.keyPath); 41 | NSAssert(object == self.observedObj, @"Action binding called with invalid object"); 42 | NSAssert(_context == (__bridge void *)(self.context), @"Action binding called with wrong context"); 43 | self.action(); 44 | } 45 | 46 | - (void)deactivateBinding { 47 | if(bindingActive) { 48 | [self.observedObj removeObserver:self forKeyPath:self.keyPath context:(__bridge void *)(self.context)]; 49 | bindingActive = NO; 50 | } 51 | } 52 | 53 | @end 54 | 55 | 56 | 57 | @implementation NSObject (ObserveActions) 58 | 59 | static char observeActionKey; 60 | 61 | - (void)addObserverAction:(ObserveAction)action forKeyPath:(NSString *)keyPath context:(id)context { 62 | // see "Associative references" in the docs for details 63 | NSMutableArray *actions = objc_getAssociatedObject(self, &observeActionKey); 64 | if(actions == nil) { 65 | actions = [NSMutableArray new]; 66 | objc_setAssociatedObject(self, &observeActionKey, actions, OBJC_ASSOCIATION_RETAIN); 67 | } 68 | 69 | ObserveActionBinding *binding = [[ObserveActionBinding alloc] initWithObservedObject:self keyPath:keyPath action:action context:context]; 70 | 71 | NSUInteger c = [actions count]; 72 | for(int i = 0; i < c; ++i) { 73 | ObserveActionBinding *b = [actions objectAtIndex:i]; 74 | if([b.keyPath isEqualToString:keyPath] && b.context == context) { 75 | [b deactivateBinding]; 76 | [actions removeObjectAtIndex:i]; 77 | break; 78 | } 79 | } 80 | 81 | [actions addObject:binding]; 82 | } 83 | 84 | - (void)removeActionObserverForKeyPath:(NSString *)keyPath context:(id)context { 85 | NSMutableArray *actions = objc_getAssociatedObject(self, &observeActionKey); 86 | if(actions == nil) { 87 | actions = [NSMutableArray new]; 88 | objc_setAssociatedObject(self, &observeActionKey, actions, OBJC_ASSOCIATION_RETAIN); 89 | } 90 | 91 | NSUInteger c = [actions count]; 92 | for(int i = 0; i < c; ++i) { 93 | ObserveActionBinding *b = [actions objectAtIndex:i]; 94 | if([b.keyPath isEqualToString:keyPath] && b.context == context) { 95 | [b deactivateBinding]; 96 | [actions removeObjectAtIndex:i]; 97 | break; 98 | } 99 | } 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /CoreFoundation/Date+Time/TimeHelpers.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeHelpers.m 3 | // Checklists 4 | // 5 | // Created by Marcel Ruegenberg on 06.02.11. 6 | // Copyright 2011 Dustlab. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // time interval in seconds 12 | NSString *timeLengthToString(NSInteger timeLength) { 13 | NSString *timeStr = @""; 14 | if(timeLength < 0) timeLength = timeLength * (-1); 15 | if(timeLength >= 60 * 60 * 24) { 16 | if(timeLength == 60 * 60 * 24) timeStr = NSLocalizedStringWithDefaultValue(@"Time Day", @"Generic", [NSBundle mainBundle], @"1 day", @"The format for describing a number of days, if the number is 1, e.g '1 day'"); 17 | else 18 | timeStr = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"Time Days", @"Generic", [NSBundle mainBundle], @"%d days", @"The format for describing a number of days, e.g '5 days'"), (timeLength / (60 * 60 * 24))]; 19 | timeLength -= (timeLength / (60 * 60 * 24)) * (60 * 60 * 24); 20 | } 21 | if(timeLength >= 60 * 60) { 22 | NSString *prefix; 23 | NSInteger m = timeLength / (60 * 60); 24 | if(! [timeStr isEqualToString:@""]) timeStr = [NSString stringWithFormat:@"%@, ", timeStr]; 25 | if(m == 1) prefix = NSLocalizedStringWithDefaultValue(@"Time Hour", @"Generic", [NSBundle mainBundle], @"1 hour", @"The format for describing a number of hours, if the number is 1, e.g '1 hour'"); 26 | else 27 | prefix = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"Time Hours", @"Generic", [NSBundle mainBundle], @"%d hours", @"The format for describing a number of hours, e.g '5 hours'"), m]; 28 | timeLength -= m * (60 * 60); 29 | timeStr = [NSString stringWithFormat:@"%@%@", timeStr, prefix]; 30 | } 31 | { 32 | NSInteger m = (NSInteger) ceil(((float)timeLength) / 60.0); 33 | if(m != 0) { 34 | NSString *prefix; 35 | if(! [timeStr isEqualToString:@""]) timeStr = [NSString stringWithFormat:@"%@, ", timeStr]; 36 | if(m == 1) prefix = NSLocalizedStringWithDefaultValue(@"Time Minute", @"Generic", [NSBundle mainBundle], @"1 minute", @"The format for describing a number of minutes, if the number is 1, e.g '1 minute'"); 37 | else 38 | prefix = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"Time Minutes", @"Generic", [NSBundle mainBundle], @"%d minutes", @"The format for describing a number of minutes, e.g '5 minutes'"), m]; 39 | timeStr = [NSString stringWithFormat:@"%@%@", timeStr, prefix]; 40 | } 41 | } 42 | return timeStr; 43 | } 44 | 45 | NSString *timeLengthToShortString(NSInteger timeLength) { 46 | NSString *prefix = @""; 47 | if(timeLength < 0) timeLength = timeLength * (-1); 48 | if(timeLength >= 60 * 60 * 24) { 49 | if(timeLength == 60 * 60 * 24) prefix = NSLocalizedStringWithDefaultValue(@"Time Day", @"Generic", [NSBundle mainBundle], @"1 day", @"The format for describing a number of days, if the number is 1, e.g '1 day'"); 50 | else 51 | prefix = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"Time Days", @"Generic", [NSBundle mainBundle], @"%d days", @"The format for describing a number of days, e.g '5 days'"), (timeLength / (60 * 60 * 24))]; 52 | timeLength = timeLength % (60 * 60 * 24); 53 | } 54 | { 55 | NSInteger hours = timeLength / (60 * 60); 56 | NSInteger minutes = (NSInteger) ceil(((float)(timeLength - (hours * 60 * 60))) / 60.0); 57 | prefix = [NSString stringWithFormat:([prefix isEqualToString:@""] ? @"%@%ld:%02ld" : @"%@, %ld:%02ld"), prefix, (long)hours, (long)minutes]; 58 | } 59 | 60 | return prefix; 61 | } 62 | 63 | NSString *timeIntervalToString(NSInteger timeInterval) { 64 | if(timeInterval == 0) return NSLocalizedStringWithDefaultValue(@"Time None", @"Generic", [NSBundle mainBundle], @"None", @"Text that describes a time interval of zero length"); 65 | 66 | NSString *format = NSLocalizedStringWithDefaultValue(@"Time Interval Format", @"Generic", [NSBundle mainBundle], @"%1$@ %2$@", @"Format for time interval strings. If the normal order would be like '5 minutes after', leave it as '1 2', if in your language the 'after' would come after the rest, change the order, i.e '2 1'"); 67 | NSString *indicator = timeInterval < 0 ? NSLocalizedStringWithDefaultValue(@"Time Before Suffix", @"Generic", [NSBundle mainBundle], @"before", @"Suffix that describes that something occured before something else, i.e the 'before' in '60 minutes before'") : NSLocalizedStringWithDefaultValue(@"Time After Suffix", @"Generic", [NSBundle mainBundle], @"after", @"Suffix that describes that something occured after something else, i.e the 'after' in 'Ring the bell 60 minutes after the event'"); 68 | NSString *timeLen = timeLengthToString(timeInterval); 69 | return [NSString stringWithFormat:format, timeLen, indicator]; 70 | } 71 | -------------------------------------------------------------------------------- /CoreFoundation/Date+Time/NSDate+Additions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Additions.m 3 | // Classes 4 | // 5 | // Created by Marcel Ruegenberg on 02.08.10. 6 | // Copyright 2010 Dustlab. All rights reserved. 7 | // 8 | 9 | #import "NSDate+Additions.h" 10 | 11 | 12 | NSDateFormatter *timeFormatter = nil; 13 | NSDateFormatter *dateFormatter = nil; 14 | NSDateFormatter *dateTimeFormatter = nil; 15 | NSDateFormatter *dateTimeFormatterLong = nil; 16 | 17 | NSCalendar *curCalendar() { 18 | static NSCalendar *calendar = nil; 19 | if(calendar == nil) calendar = [NSCalendar autoupdatingCurrentCalendar]; 20 | return calendar; 21 | } 22 | 23 | BOOL is24HourFormat() { 24 | static BOOL determined = NO; 25 | static BOOL is24Hour = NO; 26 | if(! determined) { 27 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 28 | [formatter setDateStyle:NSDateFormatterNoStyle]; 29 | [formatter setTimeStyle:NSDateFormatterShortStyle]; 30 | NSString *dateString = [formatter stringFromDate:[NSDate date]]; 31 | NSRange amRange = [dateString rangeOfString:[formatter AMSymbol]]; 32 | NSRange pmRange = [dateString rangeOfString:[formatter PMSymbol]]; 33 | is24Hour = (amRange.location == NSNotFound && pmRange.location == NSNotFound); 34 | determined = YES; 35 | } 36 | return is24Hour; 37 | } 38 | 39 | #define DATE_COMPONENTS (NSYearCalendarUnit| NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit | NSWeekdayOrdinalCalendarUnit | NSTimeZoneCalendarUnit) 40 | 41 | @implementation NSDate (DatePrinters) 42 | 43 | - (NSString *)shortTimeString { 44 | return [NSDateFormatter localizedStringFromDate:self dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle]; 45 | } 46 | 47 | - (NSString *)dateString { 48 | if(dateFormatter == nil) { 49 | dateFormatter = [[NSDateFormatter alloc] init]; 50 | [dateFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"yyyyMMMd" options:0 locale:[NSLocale currentLocale]]]; 51 | } 52 | return [dateFormatter stringFromDate:self]; 53 | } 54 | 55 | - (NSString *)weekdayString { 56 | static NSDateFormatter *wDayFormatter = nil; 57 | if(wDayFormatter == nil) wDayFormatter = [[NSDateFormatter alloc] init]; 58 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 59 | NSAssert(wDayFormatter != nil, @"Date Formatter for weekdays is nil, but should have a value."); 60 | return [[wDayFormatter standaloneWeekdaySymbols] objectAtIndex:(MAX(0, [components weekday] - 1))]; // MAX for (very) rare cases where [components weekday] is 0. probably an Apple bug. 61 | } 62 | 63 | - (NSString *)shortWeekdayString { 64 | static NSDateFormatter *wDayFormatter = nil; 65 | if(wDayFormatter == nil) wDayFormatter = [[NSDateFormatter alloc] init]; 66 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 67 | NSAssert(wDayFormatter != nil, @"Date Formatter for short weekdays is nil, but should have a value."); 68 | return [[wDayFormatter shortStandaloneWeekdaySymbols] objectAtIndex:([components weekday] - 1)]; 69 | } 70 | 71 | - (NSString *)dateTimeString { 72 | if(dateTimeFormatter == nil) { 73 | dateTimeFormatter = [[NSDateFormatter alloc] init]; 74 | if(is24HourFormat()) 75 | [dateTimeFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"yyMMMddHm" options:0 locale:[NSLocale currentLocale]]]; 76 | else 77 | [dateTimeFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"yyMMMddhma" options:0 locale:[NSLocale currentLocale]]]; 78 | } 79 | 80 | NSString *r = [dateTimeFormatter stringFromDate:self]; 81 | return r; 82 | } 83 | 84 | - (NSString *)longDateTimeString { 85 | if(dateTimeFormatterLong == nil) { 86 | dateTimeFormatterLong = [[NSDateFormatter alloc] init]; 87 | if(is24HourFormat()) 88 | [dateTimeFormatterLong setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"EEyyyyMdHm" options:0 locale:[NSLocale currentLocale]]]; 89 | else 90 | [dateTimeFormatterLong setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"EEyyyyMdhm" options:0 locale:[NSLocale currentLocale]]]; 91 | } 92 | 93 | NSString *r = [dateTimeFormatterLong stringFromDate:self]; 94 | return r; 95 | } 96 | 97 | @end 98 | 99 | 100 | @implementation NSDate (DateHelpers) 101 | 102 | - (NSDate *)nextHour { 103 | return [self dateByAddingMinutes:60]; 104 | } 105 | 106 | - (NSDate *)prevHour { 107 | return [self dateByAddingMinutes:(-60)]; 108 | } 109 | 110 | - (NSDate *)dateByAddingDays:(NSInteger)days { 111 | NSDateComponents *dC = [[NSDateComponents alloc] init]; 112 | dC.day = days; 113 | NSDate *res = [curCalendar() dateByAddingComponents:dC toDate:self options:0]; 114 | return res; 115 | } 116 | 117 | - (NSDate *)dateByAddingMinutes:(NSInteger)minutes { 118 | NSInteger secsToAdd = 60 * minutes; 119 | return [[NSDate alloc] initWithTimeInterval:secsToAdd sinceDate:self]; 120 | } 121 | 122 | - (NSDate *)prevDay { 123 | return [self dateByAddingDays:(-1)]; 124 | } 125 | 126 | - (NSDate *)nextDay { 127 | return [self dateByAddingDays:1]; 128 | } 129 | 130 | - (NSDate *)prevWeek { 131 | return [self dateByAddingDays:((-1) * 7)]; 132 | } 133 | 134 | - (NSDate *)nextWeek { 135 | return [self dateByAddingDays:(7)]; 136 | } 137 | 138 | - (NSDate *)justTime { 139 | NSCalendar *calendar = curCalendar(); 140 | static unsigned unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit; 141 | NSDateComponents *components = [calendar components:unitFlags fromDate:self]; 142 | return [calendar dateFromComponents:components]; 143 | } 144 | 145 | - (NSDate *)justHour { 146 | NSCalendar *calendar = curCalendar(); 147 | static unsigned unitFlags = NSHourCalendarUnit; 148 | NSDateComponents *components = [calendar components:unitFlags fromDate:self]; 149 | return [calendar dateFromComponents:components]; 150 | } 151 | 152 | + (NSDate *)nowTime { 153 | return [[NSDate date] justTime]; 154 | } 155 | 156 | - (NSDate *)beginOfDay { 157 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 158 | [components setHour:0]; [components setMinute:0]; [components setSecond:0]; 159 | return [curCalendar() dateFromComponents:components]; 160 | } 161 | 162 | - (NSDate *)endOfDay { 163 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 164 | [components setHour:23]; [components setMinute:59]; [components setSecond:59]; 165 | return [curCalendar() dateFromComponents:components]; 166 | } 167 | 168 | - (NSDate *)middleOfDay { 169 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 170 | [components setHour:12]; [components setMinute:0]; [components setSecond:0]; 171 | return [curCalendar() dateFromComponents:components]; 172 | } 173 | 174 | - (NSDate *)dateRoundedToMinutes:(NSUInteger)minutes { 175 | NSCalendar *calendar = curCalendar(); 176 | static unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit; 177 | NSDateComponents *comps = [calendar components:unitFlags fromDate:self]; 178 | NSInteger dateMinutes = [comps minute]; 179 | if(minutes == 0) dateMinutes = 0; 180 | else dateMinutes = (dateMinutes / minutes) * minutes; 181 | [comps setMinute:dateMinutes]; 182 | return [calendar dateFromComponents:comps]; 183 | } 184 | 185 | + (NSDate *)dateWithHour:(NSUInteger)hour minutes:(NSUInteger)minutes seconds:(NSUInteger)seconds { 186 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:[NSDate date]]; 187 | [components setHour:hour]; 188 | [components setMinute:minutes]; 189 | [components setSecond:seconds]; 190 | return [curCalendar() dateFromComponents:components]; 191 | } 192 | 193 | - (NSInteger)daysSinceDate:(NSDate *)date { 194 | // Note: if regressions with this method appear: both date and self had, before, a beginOfDay attached. 195 | 196 | // TODO: unit test: result for [[curCalendar() components:NSDayCalendarUnit fromDate:[date beginOfDay] toDate:[self beginOfDay] options:0] day]; should always be equal for a wide range of dates 197 | 198 | // NOTE: known edge cases: late on the day (e.g self = 2010-10-24 23:00:00 +0000, date = 2012-01-07 22:02:19 +0000) 199 | 200 | // to handle differences between summer and winter time, maybe remove 1.5 hours. 201 | // not sure if correct in all cases. 202 | // NSInteger result = ceil([[self beginOfDay] timeIntervalSinceDate:date] / (60 * 60 * 24.0)); 203 | //#ifndef NDEBUG 204 | // NSInteger testResult = [[curCalendar() components:NSDayCalendarUnit fromDate:[date beginOfDay] toDate:[self beginOfDay] options:0] day]; 205 | // NSAssert2(result == testResult, @"Fast daysSinceDate result differes from slow variant: %d instead of %d", result, testResult); 206 | //#endif 207 | 208 | NSInteger result = [curCalendar() components:NSDayCalendarUnit fromDate:date toDate:self options:0].day; 209 | return result; 210 | } 211 | 212 | - (NSInteger)daysSinceDateFast:(NSDate *)date { 213 | return ceil([self timeIntervalSinceDate:date] / (60 * 60 * 24.0)); 214 | } 215 | 216 | - (BOOL)isWeekend { 217 | NSCalendar *cal = curCalendar(); 218 | NSRange weekdayRange = [cal maximumRangeOfUnit:NSWeekdayCalendarUnit]; 219 | NSDateComponents *components = [cal components:NSWeekdayCalendarUnit fromDate:self]; 220 | NSUInteger weekdayOfDate = [components weekday]; 221 | 222 | return weekdayOfDate == weekdayRange.location || weekdayOfDate == weekdayRange.length; 223 | } 224 | 225 | - (BOOL)isEarlier:(NSDate *)other { 226 | return [self earlierDate:other] == self; 227 | } 228 | - (BOOL)isLater:(NSDate *)other { 229 | return [self laterDate:other] == self; 230 | } 231 | 232 | @end 233 | 234 | 235 | @implementation NSDate (Decomposition) 236 | 237 | - (NSInteger)hour { 238 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 239 | return [components hour]; 240 | } 241 | 242 | - (NSInteger)minute { 243 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 244 | return [components minute]; 245 | } 246 | 247 | - (NSInteger)day { 248 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 249 | return [components day]; 250 | } 251 | 252 | - (NSInteger)month { 253 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 254 | return [components month]; 255 | } 256 | 257 | - (NSInteger)year { 258 | NSDateComponents *components = [curCalendar() components:DATE_COMPONENTS fromDate:self]; 259 | return [components year]; 260 | } 261 | 262 | @end 263 | --------------------------------------------------------------------------------