Search code examples
objective-cnsdatenscalendarnsdatecomponents

Given an NSDate, find the last day of fourth prior month


I am trying to calculate an NSDate object based on the current date. If the current date is April 1st, 2015, I need to generate the date, December 31, 2014. If the current date is April 30th, 2015, I STILL need to generate the date, December 31, 2014. If however, it is May 1st, 2015, I need to generate January 31st, 2015. In other words, whatever month I am in, I need the date of the end of the month, from four months ago, regardless of where I am in the current month.

The code I have thus far is:

    NSCalendar *theCalendar = [NSCalendar currentCalendar];
    NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
    [dayComponent setDay:-90];
    NSDate *nextDate = [theCalendar dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];

    NSLog(@"The date I am getting is: %@", nextDate);

The above code gives me the date value of exactly 90 days prior to the current date, but I need the date to always be the end of the month that is 4 months earlier.


Solution

  • As you've already discovered, you need a starting date and a calendar:

    NSDate *startingDate = [NSDate date];
    NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
    

    You'll need the components of the current date but only down to the current month, because you don't care about the specific day within the month:

    NSDateComponents *components = [calendar
        components:NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth
        fromDate:startingDate];
    

    You say you want the last day of the fourth prior month. Since months have different numbers of days, the last day varies depending on the month. But all months have first days, and those first days are always numbered 1. So it's easiest to compute “the last day of the fourth prior month” by first going back three months:

    components.month -= 3;
    

    Then, go one day prior to that month:

    components.day = -1;
    

    Finally, you need to get clear in your head that an NSDate represents an instant in time, but a day (like “April 1st, 2015”) is an interval of time, starting and ending at specific instants. If you're going to represent a whole day using an NSDate, you're going to be storing one instant within that interval. You don't want to store the first or last instant (which will both be midnights); that causes problems for some days in some time zones. Instead, use noon as your instant:

    components.hour = 12;
    

    Now you're ready to ask the calendar for a new NSDate:

    NSDate *lastDayOfFourthPriorMonth = [calendar dateFromComponents:components];