如何从NSDate中减去持续时间,但不包括周末?

时间:2011-11-30 01:52:42

标签: objective-c cocoa nsdate nsdatecomponents

以今天为例,我如何确定它是哪个日期,230个工作日之前?

我知道如何使用while循环检查日期迭代地执行它,如果它是工作日则减去1,但我想知道是否有更好的方法。

另外,我们以周日下午1点为例,减去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 个答案:

答案 0 :(得分:4)

计算出您上一个周末的日期长度,从您的日期和偏移量中减去该金额。现在,您可以将偏移量除以5来计算偏移量中的整周数,然后将其乘以7并从日期中减去此新值。取你以前的偏移量(你除以5的偏移量)并将其修改为5,得到剩余天数。如果它大于0,则从你的日期减去该偏移量+2(周末)。

请注意,这假设每个工作日都是工作日。公司假期倾向于使该假设无效。如果您需要处理假期,那么您将遇到更严峻的问题。

更新:这是尝试修复David的代码,以实际表达这个想法:

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)

我没有详尽地测试这个,但它是基于我经常使用的一些类别方法。要确定date1和date2之间有多少个工作日(假设date1&lt; date2),请将此函数的返回值除以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.这用于查明第一周结束前的天数以及上周开始后的天数。 (计算实际上是在几秒钟内完成的。)

类别方法theFollowingWeekendthePreviousWeekend在接收日期之后的星期六早上午夜返回NSDate,在接收日期之后的星期日午夜之前返回NSDate一秒钟。这些方法假设您已经验证接收器日期不在周末。我在主要方法中处理了这个问题。寻找对象== 1或7的检查。

dateByMovingToBeginningOfDayInTimeZone:dateByMovingToEndOfDayInTimeZone:将接收日期的小时,分​​钟和秒分别设置为00:00:00和23:59:59。这是为了划定周末,从周六早上午夜到周日晚上午夜。

希望这会有所帮助。这是让我更熟悉时间和日期功能的练习。

我会将Keith Lazuka and his calendar component for iPhone归功于此代码的萌芽。

以下是使用这些功能的测试程序用户界面的屏幕截图: enter image description here

这是你的例子,运行第一个方法。感兴趣的项目突出显示。 enter image description here。为此,我进行了简单的修改以接受小数天(我在上面提到过,但没有包含在上面显示的代码中)

答案 2 :(得分:0)

使用上面的信息我做了一个简单的方法来计算两个日期之间的工作日。无法在任何地方找到这个,所以我想我会发布。

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

}