Working with NSDates and calendrical calculations can sometimes be a pain. Adding days, subtracting months, and adjusting today’s date to midnight. It may be tempting to resort to the following solution:
|
- (NSDate *)dateByAddingDays:(NSInteger)daysToAdd { // Very bad, don't do this return [NSDate dateWithTimeInterval:60 * 60 * 12 * daysToAdd sinceDate:[NSDate date]]; } |
Using the above method is brittle, bad practice and leaves significant room for improvement. Which is why I prefer using the following methods in a category on NSDate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
#import "NSDate+Normalize.h" @implementation NSDate (Normalize) + (NSDate *)midnight { NSDateComponents *components = [sharedCalendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:[NSDate new]]; return [sharedCalendar dateFromComponents:components]; } - (NSDate *)midnightDate { NSDateComponents *components = [sharedCalendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:self]; return [sharedCalendar dateFromComponents:components]; } - (NSDate *)dateByAddingDays:(NSInteger)daysToAdd { NSDateComponents *days = [[NSDateComponents alloc] init]; [days setDay:daysToAdd]; return [sharedCalendar dateByAddingComponents:days toDate:self options:0]; } - (NSDate *)dateByAddingMonths:(NSInteger)monthsToAdd { NSDateComponents *months = [[NSDateComponents alloc] init]; [months setMonth:monthsToAdd]; return [sharedCalendar dateByAddingComponents:months toDate:self options:0]; } @end |
By applying a “Normalize” category (you can call your whatever you like) on NSDate, it is now possible to apply date adjustments directly to the date you’re working with. Keep in mind that in the example above we reference sharedCalendar
. This sharedCalendar
is an instance of NSCalendar and is best obtained from a single resource (eg. AppDelegate property, Singleton object, etc.). Doing so significantly reduces overhead of these methods when called inside a loop (you don’t want to be creating an instance of NSCalendar for every iteration of the loop, as that can be quite expensive).
In fact, here’s a possible implementation of an NSCalendar category using the singleton design pattern. Include this in your NSDate+Normalize category header file, and you’ll have access to a shared calendar wherever you include this category on NSDate.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@interface NSCalendar (Shared) + (instancetype)sharedCalendar; @end @implementation NSCalendar (Shared) + (instancetype)sharedCalendar { static NSCalendar *_sharedCalendar; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedCalendar = [self currentCalendar]; }); return _sharedCalendar; } @end |
That more or less sums up some of the better ways to work with NSDate and NSCalendar for calendrical calculations and the like. As always, feel free to use the code in your personal and commercial projects.
Dima
I'm am an iOS Engineer who loves to tinker with new ideas, explore the world of mobile development, and build beautiful, high-quality applications on Apple platforms.