Adjust NSDate by a specified amount

Isn't annoying when you want to adjust a NSDate object by 3 days, or 8 hours, or even by 4 months? You have to instance a NSDateComponents object, edit what you need and then instance a new NSDate object by using a [NSCalendar dateByAddingComponents:toDate:options] method call. In my current app, I have these little adjustments littered all through-out my code. So much so that I decided it was time to make things easier. I just made a simple method that handles this for me:

- (NSDate *)adjustTimeOfDate:(NSDate *)date byAmount:(int)amount  usingPeriod:(AdjustmentPeriod)period;

It allows me to do something like this in my code:

someDateInMyApp = [NSDateCategory adjustTimeOfDate:someDateInMyApp byAmount:2 usingPeriod:AdjustByDay];

I successfully adjusted a NSDate object by two days without having to write 4 lines of code everytime, or without having to figure out how many seconds it takes to increase a NSDate object by for 2 days. I can also go backwards in time:

someDateInMyApp = [NSDateCategory adjustTimeOfDate:someDateInMyApp byAmount:-4 usingPeriod:AdjustByDay];

It makes life so much easier. In order to implement the method, we first need to have a custom enum in place that we can use to select what time frame we want to adjust by. My code was not placed into a NSDate category for my app, but I would recommend that you do that. It seems like the best place for it to go. If you need some help creating a category, check out how I did it in my Understanding Categories post.

My enum goes in my header file like such:

typedef enum adjustmentPeriod {
    AdjustByYear,
    AdjustByMonth,
    AdjustByDay,
    AdjustByHour,
    AdjustByMinute,
    AdjustBySecond
} AdjustmentPeriod;

Next, we define our method in the header file:

- (NSDate *)dateByAdjustingTimeOfDate:(NSDate *)dateToAdjust byAmount:(int)amount usingPeriodOfTime:(AdjustmentPeriod)period;

We are now ready to implement the method. It is really simple, we just instance a blank NSDateComponents and add the amount specified to the correct period of time based on what is provided as an argument. In order to determine it, we will use a switch/case statement.

- (NSDate *)dateByAdjustingTimeOfDate:(NSDate *)dateToAdjust byAmount:(int)amount usingPeriodOfTime:(AdjustmentPeriod)period {
    NSDateComponents *components = [[NSDateComponents alloc] init];

    switch (period) {
        case AdjustByYear:
            components.year = amount;
            break;
        case AdjustByMonth:
            components.month = amount;
            break;
        case AdjustByDay:
            components.day = amount;
            break;
        case AdjustByHour:
            components.hour = amount;
            break;
        case AdjustByMinute:
            components.minute = amount;
            break;
        case AdjustBySecond:
            components.second = amount;
            break;
        default:
            break;
    }

    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    return [calendar dateByAddingComponents:components toDate:dateToAdjust options:0];
}

Now you have a method that can adjust any date. Due to this being a category, you can't add properties (without a bit of grunt work) so I instance the NSCalendar object in the method itself. Due to my app using this method a lot, I chose to put it into a custom class inheriting from NSObject rather than a category so that the NSCalendar object can be a property that is shared by all method calls. Instancing a NSCalendar object is expensive, so if you plan on using this a lot through-out your code, you might want to consider abandoning the category and just sub-class NSObject so you can have a instance variable of NSCalendar to re-use.

If you find that you need to adjust multiple parts of the NSDate, such as the day and hour in one shot, you can modify our method to accept a NSDictionary object. The NSDictionary would have the periods of time you want to edit as the keys and the amount to edit as the value. So you could do something like this:

NSDictionary *adjustmentOptions = @{ @"Year" : @(4), @"Day" : @(12)};

You then loop through your dictionary keys, adjusting each period using a switch/case statement once again. This way, you only create one new NSDate object and not a series of them.