2
votes

I have a class that holds an start date and an end date, normally initialised to the firt and last second of the month.

The following function works correctly going from Nov 2010 forwards into December and back again however going backwards from November ends up with startDate set to

2010-09-30 23:00:00 GMT

Ie. a month and an hour ago.

Strangely the endDate is still correctly set to

2010-11-01 00:00:00 GMT

And going forward a month from this incorrect date also results in the correct time and date.

Is this a bug or am I doing something I shouldn't be ?

-(void) moveMonth:(NSInteger)byAmount { // Positive or negative number of months
    NSCalendar *cal = [NSCalendar currentCalendar];

 NSDateComponents *components = [[[NSDateComponents alloc] init] autorelease];
 // Update the start date
 [components setMonth:byAmount];
 NSDate *newStartDate = [cal dateByAddingComponents:components toDate:[self startDate] options:0];
 [self setStartDate:newStartDate];

 // And the end date
 [components setMonth:1];
 NSDate *newEndDate = [cal dateByAddingComponents:components toDate:[self startDate] options:0 ];
 [self setEndDate:newEndDate];
}

SOLUTION: Answer correctly pointed out this is a DST issue

If you want to deal in absolute times and date then using the following avoids any DST being involved.

    NSCalendar *cal = [[NSCalendar alloc ] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
    NSTimeZone *zone = [NSTimeZone timeZoneWithName:@"GMT"];
    [cal setTimeZone:zone];
3

3 Answers

5
votes

It is probably not a bug but something related to DST changes in October-November period.

1
votes

It would be easier to just grab the month and year of the current date, add/subtract the number of months difference, then generate a date from those new values. No need to worry about Daylight Saving changes, leap years, etc. Something like this ought to work:

-(void) moveMonth:(NSInteger)byAmount {
    NSDate *now = [NSDate date];
    NSCalendar *cal = [NSCalendar currentCalendar];

    // we're just interested in the month and year components
    NSDateComponents *nowComps = [cal components:(NSYearCalendarUnit|NSMonthCalendarUnit) 
                                        fromDate:now];
    NSInteger month = [nowComps month];
    NSInteger year = [nowComps year];

    // now calculate the new month and year values
    NSInteger newMonth = month + byAmount;

    // deal with overflow/underflow
    NSInteger newYear = year + newMonth / 12;
    newMonth = newMonth % 12;

    // month is 1-based, so if we've ended up with the 0th month, 
    // make it the 12th month of the previous year
    if (newMonth == 0) {
        newMonth = 12;
        newYear = newYear - 1;
    }

    NSDateComponents *newStartDateComps = [[NSDateComponents alloc] init];
    [newStartDateComps setYear: year];
    [newStartDateComps setMonth: month];
    [self setStartDate:[cal dateFromComponents:newDateComps]];
    [newDateComps release];

    // Calculate newEndDate in a similar fashion, calling setMinutes:59, 
    // setHour:23, setSeconds:59 on the NSDateComponents object if you  
    // want the last second of the day
}
0
votes

Here is a way to do it properly. This method returns a new NSDate after adding/subtracting month "byAmount".

-(NSDate*) moveMonth:(NSInteger)byAmount {

    NSDate *now = [NSDate date];

    NSDateComponents *components = [[NSDateComponents alloc] init];
    [components setMonth:byAmount];

    NSDate *newDate = [[NSCalendar currentCalendar] dateByAddingComponents:components toDate:now options:0];

    return newDate;
}