我如何仅向NSDate添加工作日?

时间:2011-10-24 04:53:15

标签: objective-c core-foundation

我有一个与计算Objective-C中的工作日有关的问题。

我需要将X个工作日添加到给定的NSDate

例如,如果我有一个日期: 2010年10月22日星期五,并且我添加 2 工作日,我应该得到:星期二26-辛-2010

提前致谢。

4 个答案:

答案 0 :(得分:20)

这有两个部分:

  • 周末
  • 假日

我要从其他两个帖子中抽出来帮助我。

对于周末,我需要知道一周中某个特定日期。为此,这篇文章派上用场: How to check what day of the week it is (i.e. Tues, Fri?) and compare two NSDates?

对于假期,@ vikingosegundo对这篇文章提出了非常好的建议: List of all American holidays as NSDates

首先,让我们来处理周末;

我把上面引用的帖子中的建议结合到这个漂亮的小助手函数中,该函数告诉我们日期是否是工作日:

BOOL isWeekday(NSDate * date)
{
    int day = [[[NSCalendar currentCalendar] components:NSWeekdayCalendarUnit fromDate:date] weekday];

    const int kSunday = 1;
    const int kSaturday = 7;

    BOOL isWeekdayResult = day != kSunday && day != kSaturday;

    return isWeekdayResult;
}

我们需要一种方法来将日期增加给定的天数:

NSDate * addDaysToDate(NSDate * date, int days)
{
    NSDateComponents * components = [[NSDateComponents alloc] init];
    [components setDay:days];

    NSDate * result = [[NSCalendar currentCalendar] dateByAddingComponents:components toDate:date options:0];

    [components release];

    return result;
}

我们需要一种方法来跳过周末:

NSDate * ensureDateIsWeekday(NSDate * date)
{
    while (!isWeekday(date))
    {
        // Add one day to the date:
        date = addDaysToDate(date, 1);
    }

    return date;
}

我们需要一种方法来为日期添加任意天数:

NSDate * addBusinessDaysToDate(NSDate * start, int daysToAdvance)
{
    NSDate * end = start;

    for (int i = 0; i < daysToAdvance; i++)
    {
        // If the current date is a weekend, advance:
        end = ensureDateIsWeekday(end);

        // And move the date forward by one day:
        end = addDaysToDate(end, 1);
    }

    // Finally, make sure we didn't end on a weekend:
    end = ensureDateIsWeekday(end);

    return end;
}
  • 注意;我跳过了一个明显的优化 - 您可以轻松地在当前日期添加超过一天 - 但我的帖子的目的是向您展示如何自己完成此操作 - 而不一定要提出最好的可能性溶液

现在让我们把它绑起来,看看到目前为止我们有什么:

int main() {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSDate * start = [NSDate date];
    int daysToAdvance = 10;

    NSDate * end = addBusinessDaysToDate(start, daysToAdvance);

    NSLog(@"Result: %@", [end descriptionWithCalendarFormat:@"%Y-%m-%d"
                                    timeZone:nil
                                      locale:nil]);

    [pool drain];

    return 0;
}

所以,我们已经度过了周末,现在我们需要度过假期。

引入一些RSS提要或来自其他来源的数据肯定超出了我的帖子的范围...所以,我们假设您有一些您知道的假期日期,或者根据您的工作日历,请假日休假

现在,我将使用NSArray进行此操作......但是,这仍然有很大的改进空间 - 至少它应该被排序。更好的是,某种哈希集用于快速查找日期。但是,这个例子应该足以解释这个概念。 (这里我们构造一个数组,表明从现在开始有两天和三天假期)

NSMutableArray * holidays = [[NSMutableArray alloc] init];
[holidays addObject:addDaysToDate(start, 2)];
[holidays addObject:addDaysToDate(start, 3)];

而且,这个实现与周末非常相似。我们将确保这一天不是假期。如果是,我们将提前到第二天。因此,有一组方法可以帮助解决这个问题:

BOOL isHoliday(NSDate * date, NSArray * holidays)
{
    BOOL isHolidayResult = NO;

    const unsigned kUnits = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit;
    NSDateComponents * components = [[NSCalendar currentCalendar] components:kUnits fromDate:date];

    for (int i = 0; i < [holidays count]; i++)
    {
        NSDate * holiday = [holidays objectAtIndex:i];
        NSDateComponents * holidayDateComponents = [[NSCalendar currentCalendar] components:kUnits fromDate:holiday];

        if ([components year] == [holidayDateComponents year]
            && [components month] == [holidayDateComponents month]
            && [components day] == [holidayDateComponents day])
            {
                isHolidayResult = YES;
                break;
            }
    }

    return isHolidayResult;
}

和:

NSDate * ensureDateIsntHoliday(NSDate * date, NSArray * holidays)
{
    while (isHoliday(date, holidays))
    {
        // Add one day to the date:
        date = addDaysToDate(date, 1);
    }

    return date;
}

最后,对我们的添加功能进行一些修改以考虑假期:

NSDate * addBusinessDaysToDate(NSDate * start, int daysToAdvance, NSArray * holidays)
{
    NSDate * end = start;

    for (int i = 0; i < daysToAdvance; i++)
    {
        // If the current date is a weekend, advance:
        end = ensureDateIsWeekday(end);

        // If the current date is a holiday, advance: 
        end = ensureDateIsntHoliday(end, holidays);

        // And move the date forward by one day:
        end = addDaysToDate(end, 1);
    }

    // Finally, make sure we didn't end on a weekend or a holiday:
    end = ensureDateIsWeekday(end);
    end = ensureDateIsntHoliday(end, holidays);

    return end;
}

继续尝试:

int main() {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSDate * start = [NSDate date];
    int daysToAdvance = 10;

    NSMutableArray * holidays = [[NSMutableArray alloc] init];
    [holidays addObject:addDaysToDate(start, 2)];
    [holidays addObject:addDaysToDate(start, 3)];

    NSDate * end = addBusinessDaysToDate(start, daysToAdvance, holidays);

    [holidays release];

    NSLog(@"Result: %@", [end descriptionWithCalendarFormat:@"%Y-%m-%d"
                                    timeZone:nil
                                      locale:nil]);

    [pool drain];

    return 0;
}

如果您想要整个项目,请转到:http://snipt.org/xolnl

答案 1 :(得分:3)

NSDate或NSCalendar中没有任何内容可以为您计算工作日。有问题的工作日depend to some degree on the business。在美国,“营业日”通常指工作日不是假期,但每家公司决定要观察哪些假期以及何时。例如,一些企业将小型假期的遵守情况推迟到一年的最后一周,以便员工可以在圣诞节和新年之间休假而不休假。

因此,您需要确定工作日的具体含义。然后,应该通过添加一些工作日来编写一个小方法来计算未来日期。然后使用类别向NSDate添加-dateByAddingBusinessDays:等方法。

答案 2 :(得分:1)

这个答案迟到了,但......我想我可以通过直接使用NSDateComponents来确定工作日来改善上述答案。

#define CURRENTC [NSCalendar currentCalendar]
#define CURRENTD [NSDate date]

NSInteger theWeekday;

    NSDateComponents* temporalComponents = [[NSDateComponents alloc] init];

[temporalComponents setCalendar:CURRENTC];
[temporalComponents setDay:   13];
[temporalComponents setMonth: 2];
[temporalComponents setYear: theYear];

// CURRENTC =the current calendar which determines things like how 
// many days in week for local,  also the critical “what is a weekend”
// you can also convert a date directly to components.  but the critical thing is
// to get the CURRENTC in, either way.

   case 3:{    // the case of finding business days
        NSDateComponents* startComp = [temporalComponents copy];  // start date components

        for (int i = 1; i <= offset; i++)  //offset is the number of busi days you want.
        {
            do {
                [temporalComponents setDay:   [temporalComponents day] + 1];
                NSDate* tempDate = [CURRENTC dateFromComponents:temporalComponents];
                theWeekday = [[CURRENTC components:NSWeekdayCalendarUnit fromDate:tempDate] weekday];
            } while ((theWeekday == 1) || (theWeekday == 7));
        }
        [self findHolidaysStart:startComp end:temporalComponents];  // much more involved routine.

        [startComp release];
        break;
     }

// use startComp and temporalcomponents before releasing

// temporalComponents now contain an offset of the real number of days 
// needed to offset for busi days.  startComp is just your starting date….(in components)
// theWeekday is an integer between 1 for sunday, and 7 for saturday,  (also determined
// by CURRENTC

将其转回NSDate,你就完成了。假期更多涉及..但实际上可以计算,如果只是使用联邦假期和其他一些。因为他们总是像“一月的第三个星期一”

这里是findHolidaysStart:startComp end:就像开始一样,你可以想象其余的。

// imported

    [holidayArray addObject:[CURRENTC dateFromComponents:startComp]];
    [holidayArray addObject:[CURRENTC dateFromComponents:endComp]];


// hardcoded

   dateComponents = [[NSDateComponents alloc] init];
    [dateComponents setCalendar:CURRENTC];
    [dateComponents setDay:   1];
    [dateComponents setMonth: 1];
    [dateComponents setYear: theYear];

    theWeekday = [[CURRENTC components:NSWeekdayCalendarUnit fromDate:[CURRENTC dateFromComponents:dateComponents]] weekday];

    if (theWeekday == 1) [dateComponents setDay:2];
    if (theWeekday == 7) {[dateComponents setDay:31]; [dateComponents setYear: theYear-1];}

    [holidayArray addObject:[CURRENTC dateFromComponents:dateComponents]];
    [dateComponents release];

答案 3 :(得分:1)

我接受了@ steve的回答,并添加了一种方法来计算美国所有联邦假期的日期,并将其全部列入一个类别。我已经测试过了,效果很好。看看吧。

#import "NSDate+BussinessDay.h"

@implementation NSDate (BussinessDay)

-(NSDate *)addBusinessDays:(int)daysToAdvance{
    NSDate * end = self;
    NSArray *holidays = [self getUSHolidyas];
    for (int i = 0; i < daysToAdvance; i++)
    {
        // Move the date forward by one day:
        end = [self addDays:1 toDate:end];

        // If the current date is a weekday, advance:
        end = [self ensureDateIsWeekday:end];

        // If the current date is a holiday, advance:
        end = [self ensureDateIsntHoliday:end forHolidays:holidays];
    }

    return end;
}

#pragma mark - Bussiness Days Calculations

-(BOOL)isWeekday:(NSDate *) date{
    int day = (int)[[[NSCalendar currentCalendar] components:NSWeekdayCalendarUnit fromDate:date] weekday];

    const int kSunday = 1;
    const int kSaturday = 7;

    BOOL isWeekdayResult = day != kSunday && day != kSaturday;
    return isWeekdayResult;
}

-(NSDate *)addDays:(int)days toDate:(NSDate *)date{
    NSDateComponents * components = [[NSDateComponents alloc] init];
    [components setDay:days];

    NSDate * result = [[NSCalendar currentCalendar] dateByAddingComponents:components toDate:date options:0];
    return result;
}

-(NSDate *)ensureDateIsWeekday:(NSDate *)date{
    while (![self isWeekday:date])
    {
        // Add one day to the date:
        date = [self addDays:1 toDate:date];
    }

    return date;
}

-(BOOL)isHoliday:(NSDate *)date forHolidays:(NSArray *)holidays{
    BOOL isHolidayResult = NO;

    const unsigned kUnits = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit;
    NSDateComponents * components = [[NSCalendar currentCalendar] components:kUnits fromDate:date];

    for (int i = 0; i < [holidays count]; i++)
    {
        NSDate * holiday = [holidays objectAtIndex:i];
        NSDateComponents * holidayDateComponents = [[NSCalendar currentCalendar] components:kUnits fromDate:holiday];

        if ([components year] == [holidayDateComponents year]
            && [components month] == [holidayDateComponents month]
            && [components day] == [holidayDateComponents day])
        {
            isHolidayResult = YES;
            break;
        }
    }

    return isHolidayResult;
}

-(NSDate *)ensureDateIsntHoliday:(NSDate *)date forHolidays:(NSArray *)holidays{
    while ([self isHoliday:date forHolidays:holidays])
    {
        // Add one day to the date:
        date = [self addDays:1 toDate:date];
    }

    return date;
}

-(NSArray *)getUSHolidyas{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy";

    NSString *year = [formatter stringFromDate:[NSDate date]];
    NSString *nextYear = [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:(60*60*24*365)]];
    formatter.dateFormat = @"M/d/yyyy";

    //Constant Holidays
    NSDate *newYearsDay = [formatter dateFromString:[NSString stringWithFormat:@"1/1/%@",nextYear]]; //Use next year for the case where we are adding days near end of december.
    NSDate *indDay = [formatter dateFromString:[NSString stringWithFormat:@"7/4/%@",year]];
    NSDate *vetDay = [formatter dateFromString:[NSString stringWithFormat:@"11/11/%@",year]];
    NSDate *xmasDay = [formatter dateFromString:[NSString stringWithFormat:@"12/25/%@",year]];


    //Variable Holidays
    NSInteger currentYearInt = [[[NSCalendar currentCalendar]
                                 components:NSYearCalendarUnit fromDate:[NSDate date]] year];

    NSDate *mlkDay = [self getTheNth:3 occurrenceOfDay:2 inMonth:1 forYear:currentYearInt];
    NSDate *presDay = [self getTheNth:3 occurrenceOfDay:2 inMonth:2 forYear:currentYearInt];
    NSDate *memDay = [self getTheNth:5 occurrenceOfDay:2 inMonth:5 forYear:currentYearInt]; // Let's see if there are 5 Mondays in May
    NSInteger month = [[[NSCalendar currentCalendar] components:NSYearCalendarUnit fromDate:memDay] month];
    if (month > 5) { //Check that we are still in May
        memDay = [self getTheNth:4 occurrenceOfDay:2 inMonth:5 forYear:currentYearInt];
    }
    NSDate *labDay = [self getTheNth:1 occurrenceOfDay:2 inMonth:9 forYear:currentYearInt];
    NSDate *colDay = [self getTheNth:2 occurrenceOfDay:2 inMonth:10 forYear:currentYearInt];
    NSDate *thanksDay = [self getTheNth:4 occurrenceOfDay:5 inMonth:11 forYear:currentYearInt];

    return @[newYearsDay,mlkDay,presDay,memDay,indDay,labDay,colDay,vetDay,thanksDay,xmasDay];
}

-(NSDate *)getTheNth:(NSInteger)n occurrenceOfDay:(NSInteger)day inMonth:(NSInteger)month forYear:(NSInteger)year{

    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];

    dateComponents.year = year;
    dateComponents.month = month;
    dateComponents.weekday = day; // sunday is 1, monday is 2, ...
    dateComponents.weekdayOrdinal = n; // this means, the first of whatever weekday you specified
    return [[NSCalendar currentCalendar] dateFromComponents:dateComponents];
}

@end