2011-10-24 13 views
5

Mam problem związany z obliczaniem dni roboczych w Objective-C.Jak dodać dni robocze do NSDate tylko w dni robocze?

Potrzebuję dodać X dni roboczych do danego NSDate.

Na przykład, jeśli mam datę: Piątek 22-paź-2010 i dodaję dni roboczych, należy uzyskać: Wtorek 26-paź-2010.

Z góry dziękuję.

+4

przeskocz przez weekendy i święta siebie. –

Odpowiedz

19

Istnieją dwie części to:

  • Weekendy
  • Święta

mam zamiar wyciągnąć z dwóch innych stanowiskach mi pomóc.

W weekendy będę potrzebować znać datę dnia danego dnia. Do tego, ten post jest przydatna: How to check what day of the week it is (i.e. Tues, Fri?) and compare two NSDates?

na święta, @vikingosegundo ma bardzo wielką sugestie na temat tego postu: List of all American holidays as NSDates

Najpierw zajmować weekendy;

byłem pochłonięty sugestię w poście wyżej cytowanej w tym ładnym małym funkcji pomocnika, który mówi nam, czy data jest dzień tygodnia:

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

Będziemy potrzebować sposób, aby zwiększyć datę przez daną liczbę dni:

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

Musimy sposób pominąć weekendy:

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

    return date; 
} 

I musimy znaleźć sposób, aby dodać dowolną liczbę dni do daty:

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; 
} 
  • Uwaga; Istnieje oczywista optymalizacja, którą pominąłem - możesz z łatwością dodać więcej niż jeden dzień do aktualnej daty - ale celem mojego posta jest pokazać ci, jak to zrobić samemu - i niekoniecznie wymyślić jak najlepsze rozwiązanie.

Teraz pozwala związać się i zobaczyć, że to, co mamy do tej pory:

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

Tak, mamy pokryte weekendy, teraz musimy ciągnąć w święta.

Ciągnięcie jakiegoś kanału RSS lub dane z innego źródła zdecydowanie wykraczają poza zakres mojego wpisu ... więc załóżmy, że masz pewne daty, o których wiesz, że są dniami świątecznymi lub, zgodnie z kalendarzem pracy, są dni poza.

Teraz zrobię to z NSArray ... ale, znowu, pozostawia to wiele do zrobienia - przynajmniej należy to posortować. Jeszcze lepiej, jakiś zestaw hash do szybkiego wyszukiwania dat. Ale ten przykład powinien wystarczyć do wyjaśnienia tej koncepcji. (Tutaj możemy skonstruować tablicę, która wskazuje, że są święta dwa i trzy dni od teraz)

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

I, realizacja tego będzie bardzo podobna do weekendów. Upewnimy się, że dzień nie jest świętem. Jeśli tak, przejdziemy do następnego dnia. Tak, to zbiór metod, aby pomóc z tym:

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

i:

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

    return date; 
} 

I wreszcie wprowadzić pewne modyfikacje do naszej funkcji oprócz uwzględnić święta:

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

Śmiało i spróbować go:

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

Jeśli chcesz cały projekt, napisz: http://snipt.org/xolnl

3

Nie ma nic wbudowanego w NSDate ani NSCalendar, które liczą dni robocze dla Ciebie. Dni robocze depend to some degree on the business w pytaniu. W Stanach Zjednoczonych termin "dzień roboczy" oznacza w zasadzie dni tygodnia, które nie są dniami świątecznymi, ale każda firma określa, które święta mają być obserwowane i kiedy. Na przykład niektóre firmy przenoszą przestrzeganie drobnych świąt do ostatniego tygodnia w roku, aby pracownicy mogli zostać zwolnieni między Świętami Bożego Narodzenia a Nowym Rokiem, nie biorąc urlopu.

Musisz więc dokładnie określić, co masz na myśli w ciągu dnia roboczego. W takim razie wystarczy napisać małą metodę, aby obliczyć przyszłą datę, dodając kilka dni roboczych. Następnie użyj kategorii, aby dodać metodę NSDate, taką jak -dateByAddingBusinessDays:.

1

ta odpowiedź jest spóźniona na imprezę, ale .... Pomyślałem, że mogę poprawić powyższe odpowiedzi, aby ustalić dni robocze, pracując z NSDateComponents bezpośrednio od daty w miłej pętli.

#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 

zamieniając to z powrotem w NSDate, i gotowe. Święta są znacznie bardziej zaangażowane, ale można je obliczyć, jeśli tylko wykorzystamy święta federalne i kilka innych. ponieważ zawsze są czymś w stylu "3-szego poniedziałku"

oto, co kończy się findHolidaysStart: startComp: zaczyna się tak, jak możesz wyobrazić sobie resztę.

// 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

wziąłem @ odpowiedź Steve i dodaje metodę do obliczania dni wszystkich świąt federalnych w USA i umieścić ją całą w kategorii. Przetestowałem to i działa dobrze. Sprawdź to.

#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