【问题标题】:How would I add only business days to an NSDate?我如何只将工作日添加到 NSDate?
【发布时间】:2011-10-24 04:53:15
【问题描述】:

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

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

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

提前致谢。

【问题讨论】:

  • 自己跳过周末和节假日。

标签: objective-c core-foundation


【解决方案1】:

这有两个部分:

  • 周末
  • 节假日

我将从另外两个帖子中提取来帮助我。

对于周末,我需要知道给定日期的星期几。为此,这篇文章派上用场了: 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

【讨论】:

    【解决方案2】:

    NSDate 或 NSCalendar 中没有任何内置功能可以为您计算工作日。工作日depend to some degree on the business 有问题。在美国,“工作日”通常是指非节假日的工作日,但每个公司都会确定要遵守哪些假日以及何时遵守。例如,一些企业将小假期的庆祝活动推迟到一年中的最后一周,这样员工就可以在圣诞节和元旦之间休假而无需休假。

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

    【讨论】:

      【解决方案3】:

      这个答案迟到了,但是……我想我可以通过直接使用您的日期的 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];
      

      【讨论】:

        【解决方案4】:

        我接受了@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
        

        【讨论】:

          猜你喜欢
          • 2021-10-15
          • 1970-01-01
          • 2020-03-31
          • 1970-01-01
          • 1970-01-01
          • 2011-04-01
          • 2010-12-11
          • 2013-01-28
          • 1970-01-01
          相关资源
          最近更新 更多