我有一个与计算Objective-C中的工作日有关的问题。
我需要将X
个工作日添加到给定的NSDate
。
例如,如果我有一个日期: 2010年10月22日星期五,并且我添加 2 工作日,我应该得到:星期二26-辛-2010
提前致谢。
答案 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