因此,您对工作日的定义似乎是周一到周五。我们称这些为“工作日”,而不是“周末”(即周六和周日)。
如何计算从startDate 到endDate 的工作日数并不明显,所以让我们从更简单的计算开始:从startDate 到endDate 的周数。 “周”是指从星期日开始的 7 天。
如果startDate 和endDate 落在同一周,我们将计算该周一次。如果它们落在不同的周内,我们将计算包含 startDate 的周、包含 endDate 的周以及介于两者之间的任何周。所以这是一个“包含”周数。
给定这个周数,我们显然可以乘以 5 来获得周中的工作日数。但这不一定是从startDate 到endDate 的工作日数。如果startDate 是星期日或星期一,而endDate 是星期六或星期日,那么它是正确的,否则它会计算一些应该排除的天数。
应该排除多少天?首先,考虑startDate。我们需要排除一周中startDate 之前的工作日数(0 表示周日和周一,1 表示周二,2 表示周三,3 表示周四,4 表示周五,5 表示周六)。然后考虑endDate。我们需要排除一周中endDate之后的工作日数,如果是工作日,我们需要排除endDate本身(周日和周一5个,周二4个,周三3个,周四2个, 1 代表星期五,0 代表星期六)。
考虑到所有这些,从startDate 到endDate 的工作日数的公式是
5 * inclusive count of weeks from startDate to endDate
- count of workdays preceding startDate
- count of workdays following and including endDate
例如,如果startDate 是星期一并且endDate 是同一周的星期二,则这将给出 1。如果在这种情况下你宁愿得到 2(换句话说,如果不是周末,你想把 endDate 算作工作日之一),那么公式是
5 * inclusive count of weeks from startDate to endDate
- count of workdays preceding startDate
- count of workdays following (but not including) endDate
现在让我们使用 Cocoa 出色的日历计算支持在 Objective-C 中编写它。我们将在NSCalendar 上实现这个类别:
@interface NSCalendar (Rob_Workdays)
/** I return the number of non-weekend days from startDate to endDate,
including the day containing `startDate` and excluding the day
containing `endDate`. */
- (NSInteger)Rob_countOfWorkdaysFromDate:(NSDate *)startDate
toDate:(NSDate *)endDate;
/** I return the number of non-weekend days from startDate to endDate,
including the day containing `startDate` and the day containing
`endDate`. */
- (NSInteger)Rob_countOfWorkdaysFromDate:(NSDate *)startDate
toAndIncludingDate:(NSDate *)endDate;
@end
要实现这些方法,让我们将公式直接转换为代码:
@implementation NSCalendar (Rob_Workdays)
- (NSInteger)Rob_countOfWorkdaysFromDate:(NSDate *)startDate toDate:(NSDate *)endDate {
return 5 * [self Rob_inclusiveCountOfWeeksFromDate:startDate toDate:endDate]
- [self Rob_countOfWorkdaysPrecedingDate:startDate]
- [self Rob_countOfWorkdaysFollowingAndIncludingDate:endDate];
}
- (NSInteger)Rob_countOfWorkdaysFromDate:(NSDate *)startDate toAndIncludingDate:(NSDate *)endDate {
return 5 * [self Rob_inclusiveCountOfWeeksFromDate:startDate toDate:endDate]
- [self Rob_countOfWorkdaysPrecedingDate:startDate]
- [self Rob_countOfWorkdaysFollowingDate:endDate];
}
当然,我们需要实现辅助方法,从周数开始。要计算周数,我们向后滚动 startDate 直到星期天,我们向前滚动 endDate 直到星期天。然后我们计算从startDate到endDate的天数并除以7:
/** I return the number of weeks from `startDate` to `endDate`, including
the weeks containing each date (but only counting the week once if the
same week includes both dates). */
- (NSInteger)Rob_inclusiveCountOfWeeksFromDate:(NSDate *)startDate
toDate:(NSDate *)endDate
{
startDate = [self Rob_sundayOnOrBeforeDate:startDate];
endDate = [self Rob_sundayAfterDate:endDate];
NSDateComponents *components = [self components:NSCalendarUnitDay
fromDate:startDate toDate:endDate options:0];
return components.day / 7;
}
以下是我们如何回滚到周日:
- (NSDate *)Rob_sundayOnOrBeforeDate:(NSDate *)date {
return [self Rob_dateByAddingDays:1 - [self Rob_weekdayOfDate:date]
toDate:date];
}
以下是我们如何推进到周日:
- (NSDate *)Rob_sundayAfterDate:(NSDate *)date {
return [self Rob_dateByAddingDays:8 - [self Rob_weekdayOfDate:date]
toDate:date];
}
以下是我们计算日期的工作日(从 1=星期日到 7=星期六)的方法:
- (NSInteger)Rob_weekdayOfDate:(NSDate *)date {
return [self components:NSCalendarUnitWeekday fromDate:date].weekday;
}
以下是我们如何将一些天数添加到日期:
- (NSDate *)Rob_dateByAddingDays:(NSInteger)days toDate:(NSDate *)date {
if (days != 0) {
NSDateComponents *components = [[NSDateComponents alloc] init];
components.day = days;
date = [self dateByAddingComponents:components toDate:date
options:0];
}
return date;
}
请注意,如果days 为负数,该方法会及时回滚日期。
要计算某个日期之前的工作日数,我们可以使用蛮力(因为一周中只有 7 天需要考虑),这很容易理解,或者我们可以使用一个公式,它更小但更难理解:
- (NSInteger)Rob_countOfWorkdaysPrecedingDate:(NSDate *)date {
switch ([self Rob_weekdayOfDate:date]) {
case 1: return 0;
case 2: return 0;
case 3: return 1;
case 4: return 2;
case 5: return 3;
case 6: return 4;
case 7: return 5;
default: abort();
}
// equivalently: return MIN(MAX(0, [self Rob_weekdayOfDate:date] - 2), 5);
}
计算日期后的工作日数是类似的,但我们需要两个版本,具体取决于我们是包括还是不包括给定日期:
- (NSInteger)Rob_countOfWorkdaysFollowingAndIncludingDate:(NSDate *)date {
switch ([self Rob_weekdayOfDate:date]) {
case 1: return 5;
case 2: return 5;
case 3: return 4;
case 4: return 3;
case 5: return 2;
case 6: return 1;
case 7: return 0;
default: abort();
}
// equivalently: return MIN(7 - [self Rob_weekdayOfDate:date], 5);
}
- (NSInteger)Rob_countOfWorkdaysFollowingDate:(NSDate *)date {
switch ([self Rob_weekdayOfDate:date]) {
case 1: return 5;
case 2: return 4;
case 3: return 3;
case 4: return 2;
case 5: return 1;
case 6: return 0;
case 7: return 0;
default: abort();
}
// equivalently: return MAX(6 - [self Rob_weekdayOfDate:date], 0);
}
@end