totalDaysnextMonStart、nextMonEndfullWeeksfullWeeks * 5startOffset(周六日為 0)endOffset(需扣除)fullWeeks*5 + startOffset - endOffset此法巧妙避開逐日循環,時間複雜度 O(1),支援跨年、跨世紀。
package com.date.util;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
public class WorkingDayCalculator {
public static void main(String[] args) throws Exception {
String startStr = "2006-10-01";
String endStr = "2006-10-14";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
start.setTime(sdf.parse(startStr));
end.setTime(sdf.parse(endStr));
WorkingDayCalculator calc = new WorkingDayCalculator();
System.out.println("開始:" + startStr + " (" + calc.getChineseWeek(start) + ")");
System.out.println("結束:" + endStr + " (" + calc.getChineseWeek(end) + ")");
System.out.println("工作日:" + calc.getWorkingDays(start, end));
System.out.println("休息日:" + calc.getHolidays(start, end));
}
// 總天數(支援跨年)
public int getDaysBetween(Calendar d1, Calendar d2) {
if (d1.after(d2)) { Calendar t=d1; d1=d2; d2=t; }
int days = d2.get(Calendar.DAY_OF_YEAR) - d1.get(Calendar.DAY_OF_YEAR);
int y2 = d2.get(Calendar.YEAR);
if (d1.get(Calendar.YEAR) != y2) {
d1 = (Calendar) d1.clone();
do {
days += d1.getActualMaximum(Calendar.DAY_OF_YEAR);
d1.add(Calendar.YEAR, 1);
} while (d1.get(Calendar.YEAR) != y2);
}
return days;
}
// 核心:工作日計算
public int getWorkingDays(Calendar d1, Calendar d2) {
if (d1.after(d2)) { Calendar t=d1; d1=d2; d2=t; }
int startOffset = calcOffset(d1);
int endOffset = calcOffset(d2);
Calendar nextMon1 = getNextMonday(d1);
Calendar nextMon2 = getNextMonday(d2);
int fullWeeks = getDaysBetween(nextMon1, nextMon2) / 7;
return fullWeeks * 5 + startOffset - endOffset;
}
// 偏移量:日期到下一個星期一的工作日數(周六日=0)
private int calcOffset(Calendar d) {
int dayOfWeek = d.get(Calendar.DAY_OF_WEEK); // 1=Sun ... 7=Sat
int toMon = (9 - dayOfWeek) % 7; // 到下一個周一的天數
return (toMon == 0 || toMon == 6) ? 0 : toMon - 1; // 排除周末
}
// 獲取下一個星期一
private Calendar getNextMonday(Calendar d) {
Calendar c = (Calendar) d.clone();
do { c.add(Calendar.DATE, 1); } while (c.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY);
return c;
}
public String getChineseWeek(Calendar d) {
String[] ws = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
return ws[d.get(Calendar.DAY_OF_WEEK)-1];
}
public int getHolidays(Calendar d1, Calendar d2) {
return getDaysBetween(d1, d2) - getWorkingDays(d1, d2);
}
}
Calendar.DAY_OF_WEEK:1=星期日 … 7=星期六getActualMaximum(DAY_OF_YEAR):正確處理閏年(366 天)Calendar 避免副作用修改原對象toMon = (9 - dow) % 7 統一映射到 0-6,再排除 0(周一) 和 6(周六) 對應的周末情況當前版本不包含法定節假日。擴展方向:
Set<LocalDate> holidays(含調休工作日)此演算法以數學推導替代迭代,優雅解決工作日計算問題。代碼簡潔、無第三方依賴、支援跨年,適合嵌入各類業務系統。若需節假日支援,建議引入 java.time (Java 8+) 配合節假日配置表,保持核心邏輯不變。