Using today as an example, how do I determine which date it was, 230 workdays ago?
I know how to do it iteratively with a while loop checking date and subtracting 1 if it's a workday, but I'm wondering if there is a better method.
Also, let's take a Sunday 1 PM as an example, and subtract 3 work days and 2 hours from that time. First, it doesn't make sense to subtract work-time from weekends. So it would have to move the time to 23:59:59 of Friday, and then subtract those 3 days and 2 hours.
If it's a Monday at 1:30 AM, and I'm subtracting 5 days and 3 work-hours from that time, then the result should be Friday 22:30 PM of the previous week.
Code to test Kevin's method:
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]);
Output log:
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
It should never be 12-18, since that's a Sunday.
解决方案
Figure out how long from the last weekend your date is, subtract that amount from both your date and your offset. Now you can divide your offset by 5 to figure out how many full weeks are in your offset, and then multiply that by 7 and subtract this new value from your date. Take your previous offset (the one you divided by 5) and mod it by 5, to get the number of remaining days. If it's greater than 0, subtract that offset + 2 (for the weekend) from your date.
Note, this assumes every single weekday is a workday. Corporate holidays tend to make that assumption invalid. If you need to handle holidays, you're in for a much tougher problem.
Update: Here's an attempt to fix David's code to actually express the idea here:
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];
}