6

以今天为例,如何确定 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,因为那是星期天。

4

3 回答 3

4

算出你的约会距离上周末有多长,从你的约会和你的偏移中减去这个数额。现在您可以将您的偏移量除以 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];
}
于 2011-11-30T01:59:58.943 回答
2

我没有对此进行详尽的测试,但它基于我经常使用的一些类别方法。要确定 date1 和 date2 之间有多少个工作日(假设 date1 < 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

category 方法ordinality返回接收者星期几的数字。星期日 = 1,星期六 = 7。这用于找出第一周结束之前有多少天,以及上周开始之后有多少天。(计算实际上是在几秒钟内完成的。)

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

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

希望这可以帮助。这是一个让我更加熟悉时间和日期功能的练习。

我要感谢 Keith Lazuka 和他的 iPhone 日历组件,因为这段代码的萌芽。

这是使用这些功能的测试程序用户界面的屏幕截图: 在此处输入图像描述

这是您的示例,运行第一种方法。感兴趣的项目被突出显示。 在此处输入图像描述. 为此,我进行了简单的修改以接受小数天数(我在上面提到过,但没有包含在上面显示的代码中)

于 2011-12-03T07:27:50.020 回答
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;

}
于 2013-03-29T00:44:42.987 回答