Как вычесть продолжительность из NSDate, но не включать выходные? - PullRequest
6 голосов
/ 30 ноября 2011

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

Я знаю, как сделать это итеративно с проверкой даты цикла while и вычитанием 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, поскольку это воскресенье.

Ответы [ 3 ]

4 голосов
/ 30 ноября 2011

Выясните, как долго с прошлых выходных ваша дата, вычтите эту сумму из вашей даты и вашего смещения.Теперь вы можете разделить смещение на 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];
}
2 голосов
/ 03 декабря 2011

Я не тестировал это полностью, но он основан на некоторых методах категорий, которые я регулярно использую. Чтобы определить, сколько дней недели находится между date1 и date2 (предполагается, что date1

При этом вычисление делится на количество дней до первых выходных, количество дней после последних выходных и количество дней в прошедших неделях. Выходные начинаются в субботу в 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. Для этого я сделал простую модификацию, чтобы принимать дробные дни (которые я упоминал выше, но не включал в код, показанный выше)

0 голосов
/ 29 марта 2013

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

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

}
...