计算2个时间的工作小时(分钟)
一 使用情景
比如xxx人 2020-04-30 08:00:00上班,5-1到5-5是节假日不上班,5月7日请假,2020-05-11 15:00:00后辞职。
此时我们需要计算出他们的工作小时。
二 主要逻辑
网上参考别人的计算,基本上都是2个时间相减,然后增加个单位。比如2个日期相差了几天,而我们要精确到小时或者分钟的时间,就会出现几千分钟,然后在开始时间上依次加一分钟判断是否是节假日,不是则增加一,循环上效率太低。
以下是我的思路:
忽略朝九晚五这样,都是按照24小时计算。可以在代码中修改
以情景为例则经过的时间,
2020-04-30 08:00:00 - 2020-04-30 23:59:59 【工作日】
2020-05-01 00:00:00 - 2020-05-01 23:59:59(法定节假日)
2020-05-02 00:00:00 - 2020-05-02 23:59:59(法定节假日)
2020-05-03 00:00:00 - 2020-05-03 23:59:59(法定节假日)
2020-05-04 00:00:00 - 2020-05-04 23:59:59(法定节假日)
2020-05-05 00:00:00 - 2020-05-05 23:59:59(法定节假日)
2020-05-06 00:00:00 - 2020-05-06 23:59:59 【工作日】
2020-05-07 00:00:00 - 2020-05-07 23:59:59(请假)
2020-05-08 00:00:00 - 2020-05-08 23:59:59 【工作日】
2020-05-09 00:00:00 - 2020-05-09 23:59:59(法定加班日)【工作日】
2020-05-10 00:00:00 - 2020-05-10 23:59:59(周末)
2020-05-11 00:00:00 - 2020-05-11 15:00:00 【工作日】
实际计算的时候,是5个工作日的时间相加。此时就考虑到了去除节假期,去除周末,去除自定义(如请假),增加加班日等情形。后面附上代码已经我的思路。
三 代码实现
首先判断2个日期毫秒值,相距了多少天,例子为13天
然后再开始时间上加一天,判断这天是不是工作日,如果是,开始计算当天用了多少时间
然后再加一天,依然判断是否是工作日,依次循环。
如果实际结束时间小于当天的结束时间,说明可以使用固定的结束时间,如6点下班,你5点就走了,就使用5。
代码执行时的效果就是如逻辑上那样走的。
/**
* 计算两个日期之间的工作分钟
*
* @param dt1
* 开始日期
* @param dt2
* 结束日期
* @return
*/
public static int getDifferMinute(String dt1, String dt2) {
int second = 0;
try {
String beginTime = dt1;
String endTime = dt2;
// 时间格式器
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 日期格式器
SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = sf.parse(dt1);
Date date2 = sf.parse(dt2);
Date date3 = dayFormat.parse(dt1);
long daySec = 1000*60*60*24;
Date datetemp = null;
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis(date3.getTime());
datetemp = cal.getTime();
// 计算两个日期的间隔天数
long differDays = getDifferDays(date1,date2);
// 在开始日期上,依次增加天数,判断是否是工作日,如果是工作日则增加工作小时
for (int i = 0; i <= differDays; i++) {
// 判断新的一天是否是工作日
if(i!=0) {
cal.add(Calendar.DAY_OF_MONTH, 1);
}
long l = cal.getTime().getTime() + daySec-1;
// 判断是否是工作日
boolean workindDay = isWorkindDay(cal.getTime());
if(workindDay) {
//
if(cal.getTime().before(date1)) {
beginTime = dt1;
}else {
// 如果开始时间不需要从00:00:00开始,可以修改daySec这个,我是增加了一天的毫秒值
beginTime = sf.format(cal.getTime());//
}
// 新一天的结束时间,在实际结束时间之后,则结束时间才使用实际结束时间
if(l > date2.getTime()) {
endTime = dt2;
}else {
// 如果开始时间不需要到23:59:59,可以修改这个位置,可以给cal增加指定的小时
endTime= sf.format(l);
}
// 结束时间必须在开始时间后
if(sf.parse(endTime).after(sf.parse(beginTime))) {
second +=getDateSec(beginTime,endTime);
}
}
}
} catch (ParseException e) {
logger.error("解析日期数值出错,传入的开始日期和结束日期格式出错!", e);
}
return second/60;
}
/**
* 返回两个日期之间的间隔天数
*
* @param adate
* 开始时间
* @param bdate
* 结束时间
* @return 返回之间的间隔天数
*/
private static long getDifferDays(Date adate, Date bdate) {
double d = (bdate.getTime() - adate.getTime())/ (3600 * 24 * 1000) +0.5 ;
return Math.round(d);
}
判断是否是工作日代码,其中的节假日和加班日由于涉及数据库就不黏贴了,主要就是{{春节开始时间,春节结束时间},{51开始时间,51结束时间}}这样的结构
/**
* 判断是否是工作日
* @param date1
* @return
*/
private static boolean isWorkindDay(Date date1) {
boolean result =false;
String[][] holidays = getHolidays(); //获取自定义节假日
String[][] worktimes = getWorkingDays(); //获取自定义加班日
// 计算节假日和自定义节假日
SimpleDateFormat sf1 = new SimpleDateFormat("E");
SimpleDateFormat sf2 = new SimpleDateFormat("yyyy-MM-dd");
Date datetemp = null;
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis(date1.getTime());
//cal.add(Calendar.HOUR_OF_DAY, i);
datetemp = cal.getTime();
String week = sf1.format(datetemp);
// 如果是周一到周五,为正常工作日,校验是否是节假日
if (!week.equals("星期六") && !week.equals("星期日")) {
// 判断自定义的节假日
boolean isHoliday = false;
for (int j = 1; j < holidays.length; j++) {
String d1 = holidays[j][0];
String d2 = holidays[j][1];
Date dt1 = null, dt2 = null;
try {
dt1 = sf2.parse(d1);
dt2 = sf2.parse(d2);
} catch (ParseException e) {
logger.error(e.getMessage());
logger.error("解析日期数值出错,节假日配置的开始日期和结束日期格式出错!日期值为:" + d1 + " 和 " + d2);
}
// 如果当前日期在定义的节假日范围内
if ((datetemp.after(dt1) && datetemp.before(dt2))
|| (datetemp.equals(dt1) && datetemp.equals(dt2))
|| (datetemp.compareTo(dt1) >= 0 && datetemp.compareTo(dt2) <= 0)) {
isHoliday = true;
// 如果是周一到周五,但是是自定义节假日,说明不算工作日
return false;
}
}
return true;
// 周末,判断是否是加班日
}else if (week.equals("星期六") || week.equals("星期日")) {
// 判断自定义的加班日
boolean isOvertime = false;
for (int j = 1; j < worktimes.length; j++) {
String d1 = worktimes[j][0];
String d2 = worktimes[j][1];
Date dt1 = null, dt2 = null;
try {
dt1 = sf2.parse(d1);
dt2 = sf2.parse(d2);
} catch (ParseException e) {
logger.error(e.getMessage());
logger.error("解析日期数值出错,节假日配置的开始日期和结束日期格式出错!日期值为:" + d1 + " 和 " + d2);
}
// 如果当前日期在定义的加班日日范围内
if ((datetemp.after(dt1) && datetemp.before(dt2))
|| (datetemp.equals(dt1) && datetemp.equals(dt2))
|| (datetemp.compareTo(dt1) >= 0 && datetemp.compareTo(dt2) <= 0)) {
isOvertime = true;
return isOvertime;
}
}
return false;
}
return result;
}
/**
* 计算同一个天内的2个时间,相隔多少秒
* 获取传入开始时间与结束时间相差的多少秒。传入的时间需要为同一天,如果没传结束时间,则计算到当天最后一秒
* @param date
* @return
*/
public static long getDateSec(String beginTime,String endTime) {
long sec = 0;
try {
int dayMis=1000*60*60*24;//一天的毫秒-1
SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long beginSecond = dayFormat.parse(beginTime).getTime();// 获取一天的开始的毫秒值
// 获取开始时间的毫秒值
long currentTime = timeFormat.parse(beginTime).getTime();// 返回指定日期毫秒值
long endSecond = beginSecond + dayMis -1;// 获取当天的最后一秒
// 计算结束时间的毫秒值
if(endTime != null) {
endSecond = timeFormat.parse(endTime).getTime();
}
// 日期结束时间-指定开始时间
sec = (endSecond - currentTime)/1000;
//Log.info("开始时间["+beginTime+"]--结束时间["+endTime+"]--间隔时间["+sec/60+"]分钟");
return sec;
} catch (ParseException e) {
logger.error("解析日期数值出错,传入的开始日期和结束日期格式出错!", e);
}
return sec;
}
自定义的工作日或加班日是通用的。可以配置到一起,如果针对单人的,则再是否工作日逻辑里增加,如查询此人当天是否有请假。
四 总结
因为比较急,很多东西也还是可以优化的,作为记录自己一个小功能,希望对遇到同样业务的人有所帮助吧,有什么可以优化,或者有什么错误的也欢迎指导下,或者有更好的实现。