Question

I looked all over and did not find a function that gives the number of business days between two dates.

How would I do that?

Of course it would not count the holidays, since each country has his own, but that I could do with some specific functions for my country.

EDIT: I did not ask how to get the number of days between two dates.

Was it helpful?

Solution

So your definition of business days seems to be Mondays through Fridays. Let's call these “workdays”, as opposed to “weekends” (which are Saturday and Sunday).

It is not obvious how to compute the number of workdays from a startDate to an endDate, so let's start with a simpler computation: the number of weeks from startDate to endDate. By “week” I mean a seven day period starting on a Sunday.

If startDate and endDate fall in the same week, we'll count that week once. If they fall in different weeks, we'll count the week containing startDate, the week containing endDate, and any weeks in between. So this is an “inclusive” count of weeks.

Given this number of weeks, we can obviously multiply by 5 to get the number of workdays in the weeks. But this isn't necessarily the number of workdays from startDate to endDate. If startDate is a Sunday or a Monday, and endDate is a Saturday or a Sunday, then it's correct, but otherwise it counts some days that should be excluded.

How many days should be excluded? First, consider startDate. We need to exclude the number of workdays that precede startDate in the week (0 for Sunday and Monday, 1 for Tuesday, 2 for Wednesday, 3 for Thursday, 4 for Friday, and 5 for Saturday). Then consider endDate. We need to exclude the number of workdays that follow endDate in the week, and we need to exclude endDate itself if it is a workday (5 for Sunday and Monday, 4 for Tuesday, 3 for Wednesday, 2 for Thursday, 1 for Friday, and 0 for Saturday).

Given all that, the formula for the number of workdays from startDate to endDate is

 5 * inclusive count of weeks from startDate to endDate
   - count of workdays preceding startDate
   - count of workdays following and including endDate

This will, for example, give 1 if startDate is a Monday and endDate is the Tuesday of the same week. If you would rather get 2 in that case (in other words, you want to count endDate as one of the workdays if it's not a weekend), then the formula is

 5 * inclusive count of weeks from startDate to endDate
   - count of workdays preceding startDate
   - count of workdays following (but not including) endDate

Now let's write it in Objective-C using Cocoa's excellent calendar computation support. We'll implement this category on NSCalendar:

@interface NSCalendar (Rob_Workdays)

/** I return the number of non-weekend days from startDate to endDate,
    including the day containing `startDate` and excluding the day
    containing `endDate`. */
- (NSInteger)Rob_countOfWorkdaysFromDate:(NSDate *)startDate
    toDate:(NSDate *)endDate;

/** I return the number of non-weekend days from startDate to endDate,
    including the day containing `startDate` and the day containing
    `endDate`. */
- (NSInteger)Rob_countOfWorkdaysFromDate:(NSDate *)startDate
    toAndIncludingDate:(NSDate *)endDate;

@end

To implement those methods, let's translate the formulae directly into code:

@implementation NSCalendar (Rob_Workdays)

- (NSInteger)Rob_countOfWorkdaysFromDate:(NSDate *)startDate toDate:(NSDate *)endDate {
    return 5 * [self Rob_inclusiveCountOfWeeksFromDate:startDate toDate:endDate]
        - [self Rob_countOfWorkdaysPrecedingDate:startDate]
        - [self Rob_countOfWorkdaysFollowingAndIncludingDate:endDate];
}

- (NSInteger)Rob_countOfWorkdaysFromDate:(NSDate *)startDate toAndIncludingDate:(NSDate *)endDate {
    return 5 * [self Rob_inclusiveCountOfWeeksFromDate:startDate toDate:endDate]
        - [self Rob_countOfWorkdaysPrecedingDate:startDate]
        - [self Rob_countOfWorkdaysFollowingDate:endDate];
}

Of course we need to implement the helper methods, starting with the count of weeks. To count weeks, we roll startDate backward until it's a Sunday, and we roll endDate forward until it's a Sunday. Then we compute the number of days from startDate to endDate and divide by 7:

/** I return the number of weeks from `startDate` to `endDate`, including
    the weeks containing each date (but only counting the week once if the
    same week includes both dates). */
- (NSInteger)Rob_inclusiveCountOfWeeksFromDate:(NSDate *)startDate
    toDate:(NSDate *)endDate
{
    startDate = [self Rob_sundayOnOrBeforeDate:startDate];
    endDate = [self Rob_sundayAfterDate:endDate];
    NSDateComponents *components = [self components:NSCalendarUnitDay
        fromDate:startDate toDate:endDate options:0];
    return components.day / 7;
}

Here's how we roll back to a Sunday:

- (NSDate *)Rob_sundayOnOrBeforeDate:(NSDate *)date {
    return [self Rob_dateByAddingDays:1 - [self Rob_weekdayOfDate:date]
        toDate:date];
}

And here's how we roll forward to a Sunday:

- (NSDate *)Rob_sundayAfterDate:(NSDate *)date {
    return [self Rob_dateByAddingDays:8 - [self Rob_weekdayOfDate:date]
        toDate:date];
}

Here's how we compute the weekday (from 1=Sunday to 7=Saturday) of a date:

- (NSInteger)Rob_weekdayOfDate:(NSDate *)date {
    return [self components:NSCalendarUnitWeekday fromDate:date].weekday;
}

Here's how we add some number of days to a date:

- (NSDate *)Rob_dateByAddingDays:(NSInteger)days toDate:(NSDate *)date {
    if (days != 0) {
        NSDateComponents *components = [[NSDateComponents alloc] init];
        components.day = days;
        date = [self dateByAddingComponents:components toDate:date
            options:0];
    }
    return date;
}

Note that if days is negative, that method will roll the date back in time.

To compute the number of workdays preceding a date, we can either use brute force (since there are only seven days of the week to consider), which is easy to understand, or we can use a formula, which is smaller but harder to understand:

- (NSInteger)Rob_countOfWorkdaysPrecedingDate:(NSDate *)date {
    switch ([self Rob_weekdayOfDate:date]) {
        case 1: return 0;
        case 2: return 0;
        case 3: return 1;
        case 4: return 2;
        case 5: return 3;
        case 6: return 4;
        case 7: return 5;
        default: abort();
    }
    // equivalently: return MIN(MAX(0, [self Rob_weekdayOfDate:date] - 2), 5);
}

Computing the number of workdays following a date is similar, but we need two versions, depending on whether we're including or excluding the given date:

- (NSInteger)Rob_countOfWorkdaysFollowingAndIncludingDate:(NSDate *)date {
    switch ([self Rob_weekdayOfDate:date]) {
        case 1: return 5;
        case 2: return 5;
        case 3: return 4;
        case 4: return 3;
        case 5: return 2;
        case 6: return 1;
        case 7: return 0;
        default: abort();
    }
    // equivalently: return MIN(7 - [self Rob_weekdayOfDate:date], 5);
}

- (NSInteger)Rob_countOfWorkdaysFollowingDate:(NSDate *)date {
    switch ([self Rob_weekdayOfDate:date]) {
        case 1: return 5;
        case 2: return 4;
        case 3: return 3;
        case 4: return 2;
        case 5: return 1;
        case 6: return 0;
        case 7: return 0;
        default: abort();
    }
    // equivalently: return MAX(6 - [self Rob_weekdayOfDate:date], 0);
}

@end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top