1. 程式人生 > 實用技巧 >計算兩個日期之間的天數、工時(去除週六日、年假日)

計算兩個日期之間的天數、工時(去除週六日、年假日)


需求: 有個外出申請的功能,我填寫上外出申請時間、外出返回日期, 自動計算出 外出的天數和工時(其中不包含週六日、年假日)

外出申請日期 :2020-12-24 14:54:00
外出返回日期 : 2020-12-30 12:54:00

計算天數: ?
計算工時: ?

 基本思路為:
* 1. 首先算出兩個日期之間的有效的天數(不包括年假 週六日 並且 加上調班日的天數)
* 2. 計算出開始日期 和 結束日期的 無效工時(日期中包含時、分、秒)
* 3. 實際有效的工時 = 有效的天數*每天的實際工作時間-(開始時間和結束時間這兩個節點中的無效工時)

定義介面:

/**
* 處理 天數和工時介面
*/
public interface DateService {
/**
* 獲取 時間 天數 (精確計算)
* @param startTimeStr 開始申請時間
* @param endTimeStr 結束時間
* @param returnObj 返回給前端的物件
*/
void handleDateAndHour(String startTimeStr, String endTimeStr, JSONObject returnObj);
}

實現類:


@Service("dateService")
public class DateServiceImpl implements DateService {

// 自定義上午上班時間
private final static String MORNINGHOURS = "8:30:00";
// 自定義上午下班時間
private final static String MORNINGCLOSETIME = "11:30:00";
// 自定義下午上班時間
private final static String AFTERNOONHOURS = "13:00:00";
// 自定義下午下班時間
private final static String AFTERNOONCLOSETIME = "18:00:00";


/**
* 基本思路為:
* 1. 首先算出兩個日期之間的有效的天數(不包括年假 週六日 並且 加上調班日的天數)
* 2. 計算出開始日期 和 結束日期的 無效工時(日期中包含時、分、秒)
* 3. 實際有效的工時 = 有效的天數*每天的實際工作時間-(開始時間和結束時間這兩個節點中的無效工時)
*
* @param startTimeStr
* @param endTimeStr
* @param returnObj
*/
@Override
public void handleDateAndHour(String startTimeStr, String endTimeStr, JSONObject returnObj) {
Date startTime = DateUtils.getDate(startTimeStr, DateUtils.DAFAULT_DATETIME_FORMAT);
Date endTime = DateUtils.getDate(endTimeStr, DateUtils.DAFAULT_DATETIME_FORMAT);

SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtils.DAFAULT_DATE_FORMAT);
// 初始化無效工時
int startInvalidMinute = 0;
int endInvalidMinute = 0;
// 上午上班時間(分鐘)
int morningMinute = timeToMinute(MORNINGHOURS, "HH:mm:ss");
// 上午下班時間(分鐘)
int morningCloseMinute = timeToMinute(MORNINGCLOSETIME, "HH:mm:ss");
// 下午上班時間(分鐘)
int afternoonMinute = timeToMinute(AFTERNOONHOURS, "HH:mm:ss");
// 下午下班時間(分鐘)
int afternoonCloseMinute = timeToMinute(AFTERNOONCLOSETIME, "HH:mm:ss");
// 每天的工作時間
int hour = (morningCloseMinute - morningMinute) + (afternoonCloseMinute - afternoonMinute);

// 存放節假日的集合
HashSet<String> holidaysSet = Sets.newHashSet();
// 存放調班日集合
HashSet<String> changeShiftSet = Sets.newHashSet();
int startYear = DateUtil.year(startTime);
int endYear = DateUtil.year(endTime);
// 處理假日集合 開始的年份 和結束的年份
handleDateCollection(holidaysSet, changeShiftSet, String.valueOf(startYear));
handleDateCollection(holidaysSet, changeShiftSet, String.valueOf(endYear));

//一、 假如開始和結束為同一天 並且 都不為節假日 則直接進行計算有效工時
if (DateUtils.getDate(startTimeStr, DateUtils.DAFAULT_DATE_FORMAT).compareTo(DateUtils.getDate(endTimeStr, DateUtils.DAFAULT_DATE_FORMAT)) == 0) {
double validHour = 0d;
double dateCount = 0d;
Calendar cal = Calendar.getInstance();
//判斷是否為週六日
int week = cal.get(Calendar.DAY_OF_WEEK) - 1;
// 如果當天不在年假中 並且不為週六日 或者 當天在調班日中 則為有效天
boolean state = !holidaysSet.contains(simpleDateFormat.format(startTime)) && week != 0 && week != 6;
if (state || changeShiftSet.contains(simpleDateFormat.format(startTime))) {
// 計算出當前開始日期 無效的工時
startInvalidMinute = handleInvalidMinute(startTime, true);
// 計算出當前結束日期 無效的工時
endInvalidMinute = handleInvalidMinute(endTime, false);
int sumMinute = hour - startInvalidMinute - endInvalidMinute;
// 有效工時 (換算為小時)
validHour = MathExtend.divide(sumMinute, 60, 2);
// 有效天數 (換算為天數)
dateCount = MathExtend.divide(sumMinute, 60 * (hour / 60), 2);
}
returnObj.put("dateCount", dateCount);
returnObj.put("validHour", validHour);
}

// 二、 開始和結束不為同一天的情況
// 不在年假中的有效天數
double dateCount = calLeaveDays(startTime, endTime, holidaysSet, changeShiftSet);
// 計算無效工時
// 1. 如果開始日期不在節假日日期中 或者 開始日期在 調班日 日期中 則進行計算工時

if (!holidaysSet.contains(simpleDateFormat.format(startTime)) || changeShiftSet.contains(simpleDateFormat.format(startTime))) {
// 計算出當前開始日期 無效的工時
startInvalidMinute = handleInvalidMinute(startTime, true);
}

if (!holidaysSet.contains(simpleDateFormat.format(endTime)) || changeShiftSet.contains(simpleDateFormat.format(endTime))) {
// 計算出當前結束日期 無效的工時
endInvalidMinute = handleInvalidMinute(endTime, false);
}
// 計算 有效工時 = 有效天數*每天的工時 - 無效的工時
// 所有的有效工時
int sumMinute = (int) ((hour * dateCount) - startInvalidMinute - endInvalidMinute);
// 有效工時
double validHour = MathExtend.divide(sumMinute, 60, 2);
// 最終的有效天數(按照有效工時/每天的實際工作的工時)
dateCount = MathExtend.divide(sumMinute, 60 * (hour / 60), 2);

returnObj.put("dateCount", dateCount);
returnObj.put("validHour", validHour);

}


/**
* 將時間轉換為分鐘
*
* @param time 字串時間
* @param format 自定義格式
* @return
*/
private int timeToMinute(String time, String format) {
Date xsTime = DateUtils.getDate(time, format);
int hour = DateUtil.hour(xsTime, true);
int minute = DateUtil.minute(xsTime);
int sumMinute = hour * 60 + minute;
return sumMinute;
}


/**
* 計算無效工時
*
* @param data 時間資料
* @param flag 標識 true 為開始時間,false 為結束時間
* @return
*/
public int handleInvalidMinute(Date data, boolean flag) {
// 初始化一個時間
int initMinute = 0;
// 上午上班時間(分鐘)
int morningMinute = timeToMinute(MORNINGHOURS, "HH:mm:ss");
// 上午下班時間(分鐘)
int morningCloseMinute = timeToMinute(MORNINGCLOSETIME, "HH:mm:ss");
// 下午上班時間(分鐘)
int afternoonMinute = timeToMinute(AFTERNOONHOURS, "HH:mm:ss");
// 下午下班時間(分鐘)
int afternoonCloseMinute = timeToMinute(AFTERNOONCLOSETIME, "HH:mm:ss");

int hour = DateUtil.hour(data, true);
int minute = DateUtil.minute(data);
// 傳遞進來的是時間
int dataMinute = hour * 60 + minute;
// 一、 如果是開始時間 出現五種情況 計算無效工時
if (flag) {
// 1. 開始時間 小於 上班時間 不計入無效工時
if (dataMinute < morningMinute) {
return initMinute;
}
// 2. 大於上午上班時間 小於 上午下班時間 計算無效工時
if (dataMinute >= morningMinute && dataMinute <= morningCloseMinute) {
return dataMinute - morningMinute;
}
// 3. 大於 上午下班時間 並且 小於 下午上班時間 計算無效時間
if (dataMinute > morningCloseMinute && dataMinute < afternoonMinute) {
return morningCloseMinute - morningMinute;
}
// 4. 大於等於 下午上班時間 小於 下午下班時間
if (dataMinute >= afternoonMinute && dataMinute <= afternoonCloseMinute) {
return (dataMinute - afternoonMinute) + (morningCloseMinute - morningMinute);
}
// 5. 大於下班時間
if (dataMinute > afternoonCloseMinute) {
return (morningCloseMinute - morningMinute) + (afternoonCloseMinute - afternoonMinute);
}
}


//二 、 如果是結束時間 計算無效工時
// 1. 結束時間 小於 上班時間 計算無效工時
if (dataMinute < morningMinute) {
return (morningCloseMinute - morningMinute) + (afternoonCloseMinute - afternoonMinute);
}
// 2. 大於上午上班時間 小於 上午下班時間 計算無效工時
if (dataMinute >= morningMinute && dataMinute <= morningCloseMinute) {
return (afternoonCloseMinute - afternoonMinute) + (morningCloseMinute - dataMinute);
}
// 3. 大於 上午下班時間 並且 小於 下午上班時間 計算無效時間
if (dataMinute > morningCloseMinute && dataMinute < afternoonMinute) {
return afternoonCloseMinute - afternoonMinute;
}
// 4. 大於等於 下午上班時間 小於 下午下班時間
if (dataMinute >= afternoonMinute && dataMinute <= afternoonCloseMinute) {
return afternoonCloseMinute - dataMinute;
}
// 5. 大於下班時間
if (dataMinute > afternoonCloseMinute) {
return initMinute;
}
return initMinute;
}

/**
* 處理假日集合
*
* @param holidaysSet 節假日的集合
* @param changeShiftSet 調班日集合
* @param startYear 傳入的年份
*/
private void handleDateCollection(HashSet<String> holidaysSet, HashSet<String> changeShiftSet, String startYear) {

/* TODO 根據自己的業務自行實現
* 處理節假日 和 調班日 集合
* 自己在資料庫中獲取到 自定義維護的 節假日和 調班日資料
* 將所有自定義的節假日和 調班日裝到 set集合中
* */
}

/**
* 計算 起始日期到結束日期 去除週六日 和 年假 其中包含多少天
* (傳遞進來的開始時間和 結束時間不能為同一天 如果為同一天的話,需要單獨處理)
*
* @param startTime 開始時間
* @param endTime 結束時間
* @param holidaysSet 節假日集合
* @param changeShiftSet 調班日集合
* @return
*/
public double calLeaveDays(Date startTime, Date endTime, HashSet<String> holidaysSet, HashSet<String> changeShiftSet) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtils.DAFAULT_DATE_FORMAT);

double leaveDays = 0;
//從startTime開始迴圈,若該日期不是節假日或者不是週六日則請假天數+1
Date flag = startTime;//設定迴圈開始日期
Calendar cal = Calendar.getInstance();
//迴圈遍歷每個日期
while (flag.compareTo(endTime) != 1) {
cal.setTime(flag);
//判斷是否為週六日
int week = cal.get(Calendar.DAY_OF_WEEK) - 1;
if (week == 0 || week == 6) {//0為週日,6為週六
//跳出迴圈進入下一個日期
cal.add(Calendar.DAY_OF_MONTH, +1);
flag = cal.getTime();
continue;
}
//判斷是否為節假日
try {
// 在節假日集合中 判斷是否存在於當前集合中
if (holidaysSet.contains(simpleDateFormat.format(flag))) {
//跳出迴圈進入下一個日期
cal.add(Calendar.DAY_OF_MONTH, +1);
flag = cal.getTime();
continue;
}
} catch (Exception e) {
e.printStackTrace();
}
//不是節假日或者週末,天數+1
leaveDays = leaveDays + 1;
//日期往後加一天
cal.add(Calendar.DAY_OF_MONTH, +1);
flag = cal.getTime();
}


// TODO 處理是否有 調班日的日期
Date flag2 = startTime;
Calendar cal2 = Calendar.getInstance();
while (flag2.compareTo(endTime) != 1) {
cal2.setTime(flag2);
// 判斷是否為調休日 如果為調休日的話 則再加一天
if (changeShiftSet.contains(simpleDateFormat.format(flag2))) {
leaveDays = leaveDays + 1;
}
cal2.add(Calendar.DAY_OF_MONTH, +1);
flag2 = cal2.getTime();
}

return leaveDays;
}
}