Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C: Getting the number of days in a calendar year

I am using the following NSCalendar method to get the number of days in a calendar year:

NSRange range = [gregorian rangeOfUnit:NSDayCalendarUnit 
                                inUnit:NSYearCalendarUnit 
                               forDate:date];

I am expecting a range.length return value of type NSRange which is 365 or 366 (days).

However, this code returns a range.length of 31 for a date value of 2005-06-06.

What is wrong with my code?

This is the full code snippet:

NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];

[subArray enumerateObjectsUsingBlock:
   ^(NSDate *date, NSUInteger idx, BOOL *stop) {
   NSUInteger numberOfDays = [gregorian ordinalityOfUnit:NSDayCalendarUnit
   inUnit:NSYearCalendarUnit forDate:date];
}];
like image 707
AlexR Avatar asked Feb 18 '13 16:02

AlexR


3 Answers

This calculates the number of days of a year of a given date:

NSDate *someDate = [NSDate date];

NSDate *beginningOfYear;
NSTimeInterval lengthOfYear;
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian rangeOfUnit:NSYearCalendarUnit
             startDate:&beginningOfYear
              interval:&lengthOfYear
               forDate:someDate];
NSDate *nextYear = [beginningOfYear dateByAddingTimeInterval:lengthOfYear];
NSInteger startDay = [gregorian ordinalityOfUnit:NSDayCalendarUnit
                                          inUnit:NSEraCalendarUnit
                                         forDate:beginningOfYear];
NSInteger endDay = [gregorian ordinalityOfUnit:NSDayCalendarUnit
                                        inUnit:NSEraCalendarUnit
                                       forDate:nextYear];
NSInteger daysInYear = endDay - startDay;

Sad caveat: This does not work correctly for year 1582.

The year 1582, the year when Gregor introduced the currently widespread used calendar, needed a fix to align solar with calendar years. So they went with the pragmatic solution: They just dropped October 5-14. (They were not crazy enough to change weekdays, too). As a result the year 1582 only has 355 days.

Addendum: The code above only works correctly for years after 1582. It returns 365 days for the year 1500, for example, even though this year was a leap year in the then used julian calendar. The gregorian calendar starts at October 15, 1582. Computations made on the gregorian calendar are just not defined before that date. So in this way Apple's implementation is correct. I'm not aware of a correct implementation for years before 1583 on Cocoa.

like image 171
Nikolai Ruhe Avatar answered Oct 14 '22 17:10

Nikolai Ruhe


Here's a relatively clean version. It's a category on NSCalendar.

- (NSInteger) daysInYear:(NSInteger) year {
    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
    dateComponents.year = year;
    dateComponents.month = dateComponents.day = 1;
    NSDate *firstOfYear = [self dateFromComponents:dateComponents];
    dateComponents.year = year + 1;
    NSDate *firstOfFollowingYear = [self dateFromComponents:dateComponents];

    return [[self components:NSDayCalendarUnit
                   fromDate:firstOfYear
                     toDate:firstOfFollowingYear
                     options:0] day];    
}

Thanks to AKV for pointing out that -components:fromDate:toDate:options will work in this case.

This doesn't seem to work for 1582 or prior, which per Nikolai Ruhe's answer is because Gregorian calendar calculations simply aren't defined prior to then.

like image 26
paulmelnikow Avatar answered Oct 14 '22 18:10

paulmelnikow


What about finding number of days in a given year as: Although this is not the relative solution for the question.

-(NSInteger)daysBetweenTwoDates:(NSDate *)fromDateTime andDate:(NSDate *)toDateTime{

    NSDate *fromDate;
    NSDate *toDate;

    NSCalendar *calendar = [NSCalendar currentCalendar];

    [calendar rangeOfUnit:NSDayCalendarUnit startDate:&fromDate interval:NULL forDate:fromDateTime];
    [calendar rangeOfUnit:NSDayCalendarUnit startDate:&toDate interval:NULL forDate:toDateTime];

    NSDateComponents *difference = [calendar components:NSDayCalendarUnit fromDate:fromDate toDate:toDate options:0];

    return [difference day]; 
}

-(void)someMethod {

    NSString *yourYear=@"2012";

    NSDate *date1 = [NSDate dateWithString:[NSString stringWithFormat:@"%@-01-01 00:00:00 +0000",yourYear]];
    NSDate *date2 = [NSDate dateWithString:[NSString stringWithFormat:@"%@-12-31 00:00:00 +0000",yourYear]];

    NSInteger numberOfDays=[self daysBetweenTwoDates:date1 andDate:date2];    

    NSLog(@"There are %ld days in between the two dates.", numberOfDays);
}            

Edit:

As dateWithString: is deprecated, you can use

NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
[dateFormatter dateFormat]=@"dd-MM-yyyy";
NSDate *date=[dateFormatter dateFromString:dateString];

to form date1 and date2

like image 44
Anoop Vaidya Avatar answered Oct 14 '22 17:10

Anoop Vaidya