Как бы я добавил в NSDate только рабочие дни? - PullRequest
5 голосов
/ 24 октября 2011

У меня есть проблема, связанная с подсчетом рабочих дней в Objective-C.

Мне нужно добавить X рабочих дней к данному NSDate.

Например, если у меня есть дата: Пятница 22-окт-2010 , и я добавляю 2 рабочих дней, я должен получить: Вторник 26-Окт-2010 .

Заранее спасибо.

Ответы [ 4 ]

20 голосов
/ 24 октября 2011

Это две части:

  • Выходные
  • Праздники

Я собираюсь вытащить две другие должности, чтобы выручить меня.

В выходные дни мне нужно знать день недели на определенную дату.Для этого пригодится этот пост: Как проверить, какой это день недели (т.е. вторник, пятница?) И сравнить два NSDates?

Для праздников у @vikingosegundo естьдовольно большое предложение на этот пост: Список всех американских праздников как 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

3 голосов
/ 24 октября 2011

Нет ничего встроенного в NSDate или NSCalendar, которое считало бы для вас рабочие дни.Рабочие дни зависят в некоторой степени от рассматриваемого бизнеса .В США «рабочий день» обычно означает будние дни, которые не являются выходными, но каждая компания определяет, какие праздники соблюдать и когда.Например, некоторые предприятия переносят соблюдение незначительных праздников на последнюю неделю года, чтобы сотрудники могли выходить между Рождеством и Новым годом, не выходя в отпуск.

Итак, вам необходимо решить, что именно выимею ввиду под рабочий день.Тогда должно быть достаточно просто написать небольшой метод для вычисления будущей даты, добавив некоторое количество рабочих дней.Затем используйте категорию, чтобы добавить метод типа -dateByAddingBusinessDays: в NSDate.

1 голос
/ 11 июля 2014

Я взял ответ @ 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
1 голос
/ 02 марта 2012

этот ответ опоздал на вечеринку, но ...Я подумал, что мог бы улучшить приведенные выше ответы, чтобы определить рабочие дни, работая с 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, и все готово.Каникулы гораздо более сложны ... но на самом деле могут быть рассчитаны, если использовать только федеральные праздники и несколько других.потому что они всегда что-то вроде «3-го понедельника января»

, вот как 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];
...