Как я могу вычесть продолжительность из NSDATE, но не включать выходные?

StackOverflow https://stackoverflow.com/questions/8320213

Вопрос

Используя сегодня, в качестве примера, как я могу определить, какая дата, 230 рабочих дней назад?

Я знаю, как сделать это итеративно с датой проверки цикла и вычитание 1, если это рабочий день, но мне интересно, есть ли лучший метод.

Кроме того, давайте возьмем воскресенье в 13:00 в качестве примера и вычнем 3 рабочих дня и 2 часа с тех пор. Во-первых, не имеет смысла вычитать рабочее время из выходных. Таким образом, это должно было бы перенести время до 23:59:59 пятницы, а затем вычесть эти 3 дня и 2 часа.

Если это понедельник в 1:30, и я вычитаю 5 дней и 3 рабочих часа с того времени, то результат должен быть в пятницу, 22:30 вечера предыдущей недели.


Код для проверки метода Кевина:

NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *dc = [[NSDateComponents new] autorelease];
dc.month = 12;
dc.day = 19;
dc.year = 2011;
dc.hour = 1;
dc.minute = 0;
dc.second = 0;

NSDate *date = [cal dateFromComponents:dc];

NSLog(@"%@", [date descriptionWithCalendarFormat:nil timeZone:nil locale:nil]);
date = dateBySubtractingWorkOffset(date, 0, 2);
NSLog(@"%@", [date descriptionWithCalendarFormat:nil timeZone:nil locale:nil]);

Выводный журнал:

2011-12-02 16:33:46.878 otest[7124:707] 2011-12-19 01:00:00 -0500
2011-12-02 16:33:47.659 otest[7124:707] 2011-12-18 23:00:00 -0500

Это никогда не должно быть 12-18, так как это воскресенье.

Это было полезно?

Решение

Выясните, как долго с последних выходных - ваша дата, вычтите эту сумму как с вашей даты, так и с вашего смещения. Теперь вы можете разделить свое смещение на 5, чтобы выяснить, сколько полных недель в вашем смещении, а затем умножить это на 7 и вычесть это новое значение с вашей даты. Возьмите свое предыдущее смещение (тот, который вы разделили на 5) и мод на 5, чтобы получить количество оставшихся дней. Если это больше 0, вычтите это смещение + 2 (на выходные) с вашей даты.

Обратите внимание, что это предполагает, что каждый будний день - рабочий день. Корпоративные праздники, как правило, делают это предположение недействительным. Если вам нужно справиться с праздниками, у вас ждет гораздо более сложная проблема.

Обновлять: Вот попытка исправить код Дэвида, чтобы фактически выразить идею здесь:

NSDate *dateBySubtractingWorkOffset(NSDate *date, NSUInteger days, NSUInteger hours) {
    const int secsInHour = 60*60;
    const int secsInDay = 24*secsInHour;
    NSTimeInterval offset = days*secsInDay + hours*secsInHour;
    NSCalendar *cal = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];

    // figure out distance from last weekend
    {
        NSUInteger units = NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSWeekdayCalendarUnit;
        NSDateComponents *dc = [cal components:units fromDate:date];
        if (dc.weekday == 1 || dc.weekday == 7) {
            // we're in the weekend already. Let's just back up until friday
            // and then we can start our calculations there
        } else {
            // figure out our offset from sunday 23:59:59
            dc.day -= (dc.weekday - 1);
            dc.weekday = 1;
            dc.hour = 23;
            dc.minute = 23;
            dc.second = 23;
            NSDate *sunday = [cal dateFromComponents:dc];
            NSTimeInterval newOffset = [date timeIntervalSinceDate:sunday];
            if (offset < newOffset) {
                // our offset doesn't even go back to sunday, we don't need any calculations
                return [date dateByAddingTimeInterval:-offset];
            }
            offset -= [date timeIntervalSinceDate:sunday];
            // Now we can jump back to Friday with our new offset
        }
        // Calculate last friday at 23:59:59
        dc.day -= (dc.weekday % 7 + 1);
        dc.hour = 23;
        dc.minute = 59;
        dc.second = 59;
        date = [cal dateFromComponents:dc];
    }

    // We're now set to Friday 23:59:59
    // Lets figure out how many weeks we have
    int secsInWorkWeek = 5*secsInDay;
    NSInteger weeks = (NSInteger)trunc(offset / secsInWorkWeek);
    offset -= weeks*secsInWorkWeek;
    if (weeks > 0) {
        // subtract that many weeks from the date
        NSDateComponents *dc = [[NSDateComponents alloc] init];
        dc.week = -weeks;
        date = [cal dateByAddingComponents:dc toDate:date options:0];
        [dc release];
    }
    // now we can just subtract our remaining offset from the date
    return [date dateByAddingTimeInterval:-offset];
}

Другие советы

Я не исчерпывающе проверяю это, но это основано на некоторых методах категории, которые я регулярно использую. Чтобы определить, сколько будних дней находится между датой 1 и датой 2 (предполагает дату 1 <дата 2), разделите возвращаемое значение этой функции на 24*60*60 (количество секунд за день).

Это разбивает расчет на количество дней до первых выходных, количество дней после последних выходных и количество дней за прошедшие недели. Выходные начинаются в субботу в 00:00:00 и заканчиваются в воскресенье в 23:59:59. Как правило, вы хотите не предположить, что в день в нем 24 часа, потому что могут быть особые случаи, связанные со временем сбережений дневного света. Поэтому я рекомендую использовать NSCAlendar для расчета интервалов времени, когда это важно. Но это происходит по выходным, так что это не важно для этого случая.

Здесь есть два метода. Первый возвращает дату окончания NSDATE, если вы предоставите дату начала и количество рабочих дней (будние дни), на которые вы хотите продлить. (Более ранняя дата возвращается, если количество рабочих дней отрицательное.) Второе возвращает количество секунд, которые соответствуют количеству рабочих дней (включая дробные дни) между двумя данными датами NSDATE.

Я пытался сохранить расчеты в рамках часового пояса, но по умолчанию в системе часовой пояс. (Кстати, если вы хотите рассчитать с дробными днями, измените weekdays параметр к плаву. Или вы можете рассчитать, используя параметр за считанные секунды. Если это так, то также измените расчет TotalInterval в первом методе. Вам не придется конвертировать в секунды. Все последующие расчеты в этом методе выполняются в секундах.)

- (NSDate*) calculateWeekDaysEndDateFrom:(NSDate*)_date1 and:(int)weekdays {

NSTimeInterval dayInterval = 24*60*60;

NSTimeInterval totalInterval = dayInterval * (float) weekdays;
NSTimeInterval secondsBeforeWeekend;
NSTimeInterval secondsAfterWeekend;
NSTimeInterval secondsInInterveningWeeks;
int numberOfWeeks;

NSDate *dateOfFirstSaturdayMorning;
NSDate *dateOfLastSundayNight;

NSDate *finalDate;

if (weekdays >0) {
    dateOfFirstSaturdayMorning = [_date1 theFollowingWeekend];
    secondsBeforeWeekend = [dateOfFirstSaturdayMorning timeIntervalSinceDate:_date1];  

    numberOfWeeks = (int)((totalInterval - secondsBeforeWeekend)/(5.0 * dayInterval));
    secondsInInterveningWeeks = 5 * (float)(numberOfWeeks * dayInterval);
    secondsAfterWeekend = totalInterval - secondsBeforeWeekend - secondsInInterveningWeeks;

    dateOfLastSundayNight = [[dateOfFirstSaturdayMorning dateByAddingDays:7*numberOfWeeks+2] dateByAddingTimeInterval:-1]; // move from saturday morning to monday morning, then back off 1 second

    finalDate = [dateOfLastSundayNight dateByAddingTimeInterval:secondsAfterWeekend];
}
else {
    dateOfLastSundayNight = [_date1 thePreviousWeekend];
    secondsAfterWeekend = [date1 timeIntervalSinceDate:dateOfLastSundayNight];

    numberOfWeeks = (int)((-totalInterval - secondsAfterWeekend)/(5.0 * dayInterval));
    secondsInInterveningWeeks = 5 * (float)(numberOfWeeks * dayInterval);
    dateOfFirstSaturdayMorning = [[dateOfLastSundayNight dateByAddingDays:-(7*numberOfWeeks+2)] dateByAddingTimeInterval:+1];
    secondsBeforeWeekend = -totalInterval - secondsInInterveningWeeks - secondsAfterWeekend;

    finalDate = [dateOfFirstSaturdayMorning dateByAddingTimeInterval:-secondsBeforeWeekend];
}

    NSLog(@"dateOfFirstSaturdayMorning = %@", [dateOfFirstSaturdayMorning descriptionWithLocale:[NSLocale currentLocale]]);
    NSLog(@"dateOfLastSundayNight = %@",[dateOfLastSundayNight descriptionWithLocale:[NSLocale currentLocale]]);

    NSLog(@"date 1 = %@", date1);
    NSLog (@"daysBeforeWeekend = %.2f", secondsBeforeWeekend/((float)dayInterval));
    NSLog (@"daysBetweenWeekends = %.2f", secondsInInterveningWeeks/((float)(dayInterval)));
    NSLog (@"daysAfterWeekend = %.2f", secondsAfterWeekend/((float)dayInterval));
    NSLog (@"numberOfWeekdays = %.2f", (secondsBeforeWeekend + secondsInInterveningWeeks + secondsAfterWeekend)/((float)dayInterval));

    NSLog(@"endDateFromWeekdays = %@", [finalDate descriptionWithLocale:[NSLocale currentLocale]]);

    return finalDate;

}

- (NSTimeInterval) calculateWeekdaysFrom:(NSDate*)_date1 and:(NSDate*)_date2 {
    if (_date1 && _date2) {

        NSTimeInterval secondsBeforeWeekend;
        NSTimeInterval secondsAfterWeekend;

        NSDate *dateOfFirstSaturdayMorning;
        NSDate *dateOfLastSundayNight;

        NSTimeInterval dayInterval = 24*60*60; // This isn't always true, e.g., if daylight savings intervenes. (But that happens on the weekend in most places.)

        // see if they are in the same week 
        if (([_date1 ordinality] < [_date2 ordinality]) && [_date2 timeIntervalSinceDate:_date1] <= 5*dayInterval) {
            return [_date2 timeIntervalSinceDate:_date1];
        }

        // time interval before a first weekend
        if ([_date1 ordinality] == 1 || [_date1 ordinality] == 7) {
            secondsBeforeWeekend = 0;
            dateOfFirstSaturdayMorning = _date1; // This is just a convenience. It's not true. But, later, rounding takes place to deal with it.
        }
        else {
            dateOfFirstSaturdayMorning = [_date1 theFollowingWeekend];
            secondsBeforeWeekend = [dateOfFirstSaturdayMorning timeIntervalSinceDate:_date1];
        }


        int ordDate2 = [_date2 ordinality];
        int ordFirstSaturday = [dateOfFirstSaturdayMorning ordinality];

        // time interval after a last weekend
        if ([_date2 ordinality] == 1 || [_date2 ordinality] == 7) {
            secondsAfterWeekend = 0;
            dateOfLastSundayNight = _date2;  // Again, this is just a convenience. It's not true.
        }
        else {
            dateOfLastSundayNight = [_date2 thePreviousWeekend];
            secondsAfterWeekend = [_date2 timeIntervalSinceDate:dateOfLastSundayNight];
        }

        NSTimeInterval intervalBetweenWeekends = [dateOfLastSundayNight timeIntervalSinceDate:dateOfFirstSaturdayMorning];

        int numberOfWeeks = (int) (intervalBetweenWeekends/(7*dayInterval));
        int secondsInInterveningWeeks = (float) (5*dayInterval*numberOfWeeks);

        NSLog(@"date 1 = %@", [_date1 descriptionWithLocale:[NSLocale currentLocale]]);
        NSLog(@"date 2 = %@", [_date2 descriptionWithLocale:[NSLocale currentLocale]]);

        NSLog(@"dateOfFirstSaturdayMorning = %@", [dateOfFirstSaturdayMorning descriptionWithLocale:[NSLocale currentLocale]]);
        NSLog(@"dateOfLastSundayNight = %@",[dateOfLastSundayNight descriptionWithLocale:[NSLocale currentLocale]]);

        NSLog (@"daysBeforeWeekend = %.2f", secondsBeforeWeekend/((float)dayInterval));
        NSLog (@"daysBetweenWeekends = %.2f", secondsInInterveningWeeks/((float)(dayInterval)));
    NSLog (@"daysAfterWeekend = %.2f", secondsAfterWeekend/((float)dayInterval));
        NSLog (@"numberOfWeekdays = %.2f", (secondsBeforeWeekend + secondsInInterveningWeeks + secondsAfterWeekend)/((float)dayInterval));
        return secondsBeforeWeekend + secondsInInterveningWeeks + secondsAfterWeekend;
   }

    else 
        return 0;
}

Файлы для методов категории на NSDATE являются NSDATE+help.h

@interface NSDate (help)

+ (NSDate *) LSExtendedDateWithNaturalLanguageString:(NSString *)dateString WithFormatter:(NSDateFormatter*)dateFormatter;
- (NSUInteger)ordinality;

- (NSDate*) theFollowingWeekend;
- (NSDate *) thePreviousWeekend;

- (NSDate *) dateByAddingDays:(NSInteger) numberOfDays;

- (NSDate *) dateByMovingToBeginningOfDayInTimeZone:(NSTimeZone*)tz;
- (NSDate *) dateByMovingToEndOfDayInTimeZone:(NSTimeZone*)tz;

@end

и nsdate+help.m

#import "NSDate+help.h"

@implementation NSDate (help) 


// thrown in for testing
+ (NSDate *) LSExtendedDateWithNaturalLanguageString:(NSString *)dateString WithFormatter:(NSDateFormatter*)dateFormatter{

    [dateFormatter setDateFormat:@"yyyy-MM-dd HHmm"];
    [dateFormatter setLocale:[NSLocale currentLocale]];

    //NSDate *formattedDate = [dateFormatter dateFromString:@"2008-12-3T22-11-30-123"];

    return [dateFormatter dateFromString:dateString];
}

- (NSUInteger)ordinality {
    NSCalendar *calendar = [NSCalendar currentCalendar];
    [calendar setTimeZone:[NSTimeZone systemTimeZone]];
    return [calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:self];
}

- (NSDate*) theFollowingWeekend {

    NSUInteger myOrdinality = [self ordinality];
    NSDate *dateOfFollowingWeekend = [self dateByAddingDays:(7-myOrdinality)%7];

    return [dateOfFollowingWeekend dateByMovingToBeginningOfDayInTimeZone:(NSTimeZone*)nil];
}

- (NSDate *) thePreviousWeekend {
    NSUInteger myOrdinality = [self ordinality];
    NSDate *dateOfPreviousWeekend = [self dateByAddingDays:(1-myOrdinality)];

    return [dateOfPreviousWeekend dateByMovingToEndOfDayInTimeZone:(NSTimeZone*)nil];
}

- (NSDate *) dateByAddingDays:(NSInteger) numberOfDays {
    NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
    dayComponent.day = numberOfDays;

    NSCalendar *theCalendar = [NSCalendar currentCalendar];
    return [theCalendar dateByAddingComponents:dayComponent toDate:self options:0];
}

- (NSDate *) dateByMovingToBeginningOfDayInTimeZone:(NSTimeZone*)tz {

    NSTimeZone *timezone;
    if (tz)
        timezone = tz;
    else
        timezone = [NSTimeZone systemTimeZone];

    unsigned int flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
    NSDateComponents* parts = [[NSCalendar currentCalendar] components:flags fromDate:self];
    [parts setHour:0];
    [parts setMinute:0];
    [parts setSecond:0];
    NSCalendar *calendar = [NSCalendar currentCalendar];
    [calendar setTimeZone:timezone];
    return [calendar dateFromComponents:parts];
}

- (NSDate *)dateByMovingToEndOfDayInTimeZone:(NSTimeZone*)tz {

    NSTimeZone *timezone;
    if (tz)
        timezone = tz;
    else
        timezone = [NSTimeZone systemTimeZone];

        unsigned int flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
        NSDateComponents* parts = [[NSCalendar currentCalendar] components:flags fromDate:self];
        [parts setHour:23];
        [parts setMinute:59];
        [parts setSecond:59];
        NSCalendar *calendar = [NSCalendar currentCalendar];
        [calendar setTimeZone:timezone];
        return [calendar dateFromComponents:parts];
    }

@end

Метод категории ordinality Возвращает номер за день недели приемника. Воскресенье = 1, суббота = 7. Это используется, чтобы выяснить, сколько дней в конце первой недели и сколько дней после начала прошлой недели. (Расчеты фактически выполняются в секундах.)

Методы категории theFollowingWeekend а также thePreviousWeekend Верните NSDATE в полночь в субботу утром, которая следует за датой приемника, и NSDATE за секунду до полуночи в воскресенье, которая следует за датой получателя. Эти методы предполагают, что вы уже подтвердили, что дата получателя не на выходных. Я справился с этим в основных методах. Ищите проверки ординальности == 1 или 7.

dateByMovingToBeginningOfDayInTimeZone: а также dateByMovingToEndOfDayInTimeZone: Установите часы, минуты и секунды получателя даты до 00:00:00 и 23:59:59 соответственно. Это для разграничения выходных, которые проходят с полуночи в субботу до полуночи в воскресенье вечером в часовом поясе.

Надеюсь это поможет. Это было для меня упражнением, чтобы стать более знакомым с функциональностью времени и даты.

Я буду заслужить Кит Лазука и его компонент календаря для iPhone для прорастания этого кода.

Вот снимок экрана пользовательского интерфейса тестовой программы, который использует эти функции:enter image description here

Вот ваш пример, запустите первый метод. Предметы, представляющие интерес, выделены.enter image description here. Анкет Для этого я сделал простую модификацию, чтобы принять дробные дни (которые я упоминал выше, но не включил в код, показанный выше)

Используя информацию сверху, я сделал простой метод для работы в будние дни между двумя датами. Не мог найти этого нигде, поэтому я подумал, что опубликую.

    - (NSInteger)endDate:(NSDate *)eDate minusStartDate:(NSDate *)sDate{

    int weekDaysCount;
    weekDaysCount = 0;

    //A method that calculates how many weekdays between two dates
    //firstcompare dates to make sure end date is not in the past
    //using the NScomparisonresult and the NSDate compare: method

    NSComparisonResult result = [sDate compare:eDate];

    if (result == NSOrderedDescending) {
        eDate = sDate;
        //NSLog(@"invalid date so set to end date to start date");
    }

    //Work out the number of days btween the twodates passed in
    //first set up a gregorian calander

    NSCalendar *gregorian = [[NSCalendar alloc]
                             initWithCalendarIdentifier:NSGregorianCalendar];

    NSUInteger unitFlags = NSDayCalendarUnit;

    NSDateComponents *components = [gregorian components:unitFlags
                                                fromDate:sDate
                                                  toDate:eDate options:0];

    //get the number of days
    NSInteger days = [components day];

    //now loop through the days and only count the weekdays

    while (days > 0) {//while days are greater than 0

//        NSLog(@"days = %i", days);


        //get the weekday number of the start date
        NSDateComponents *comps = [gregorian components:NSWeekdayCalendarUnit fromDate:sDate];
//        NSLog(@"sDate %@", sDate);

        int weekday = [comps weekday];

//        NSLog(@"Comps Weekday = %i", weekday);

        //Test for a weekday - if its not a Saturday or Sunday
        if ((weekday!=7) && (weekday !=1)){

            //increase weekDays count
            weekDaysCount ++;
//            NSLog(@"weekDaysCount is %i", weekDaysCount);
//            NSLog(@"-------------------------");


        }

        //decrement the days
        days -=1;

        //increase the date so the next day can be tested
        sDate = [sDate dateByAddingTimeInterval:(60 * 60 * 24)];

    }

    return weekDaysCount;

}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top