如何仅向 NSDate 添加工作日?

问题描述 投票:0回答:5

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

我需要将

X
个工作日添加到给定的
NSDate

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

提前致谢。

objective-c core-foundation
5个回答
21
投票

这有两个部分:

  • 周末
  • 假期

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

对于周末,我需要知道给定日期是一周中的哪一天。为此,这篇文章派上用场: 如何检查今天是星期几(即星期二、星期五?)并比较两个 NSDate?

对于假期,@vikingosegundo 在这篇文章中有一个非常好的建议: 所有美国假期的 NSDate 列表

首先,我们来处理一下周末;

我已将上面引用的帖子中的建议纳入这个漂亮的小帮助函数中,该函数告诉我们日期是否是工作日:

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


3
投票

NSDate 或 NSCalendar 中没有内置任何内容可以为您计算工作日。工作日在某种程度上取决于相关业务。在美国,“工作日”通常是指非节假日的工作日,但每个公司都会确定要遵守哪些节假日以及何时遵守节假日。例如,一些企业将小假期推迟到一年中的最后一周,以便员工可以在圣诞节和元旦之间休息,而无需休假。

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

-dateByAddingBusinessDays:
的方法。


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];

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

0
投票

我只需要在周末跳来跳去,所以我真的不想解析 ObjC 答案,只获取相关的实现,并将它们转换为 Swift,所以我在 Swift 5 中编写了自己的代码:

extension Date {
    func addingBusinessDays(_ dayCount: Int) -> Date {
        let calendar = Calendar.current

        // Monday through Friday.
        let weekdaySet = IndexSet([2, 3, 4, 5, 6])

        var date = self
        var daysToSkip = dayCount
        while daysToSkip > 0 {
            date = calendar.date(byAdding: .day, value: 1, to: date)!
            if weekdaySet.contains(calendar.component(.weekday, from: date)) {
                daysToSkip -= 1
            }
        }
        return date
    }
}

由于我并没有真正尝试理解前面的答案,所以这个粗略的实现可能会更慢,它只是迭代,直到达到开发人员期望的工作日跳跃数量。

我确信日历的查找下周末的功能可以用来优化它,但对于较小的时间跳过,延迟可以忽略不计。 🤷

© www.soinside.com 2019 - 2024. All rights reserved.