LocalDateTime 计算时间差,去除节假日

背景:项目中涉及到需要计算两个时间节点的时间差,一开始设想的是精确到小时数,考虑上下班和午休,以及节假日。


自己去写是不会的,于是搜了一下,还真有现成的:Java 计算工作时间 除去周末、节假日_findhours_夜色星空的博客-CSDN博客

这位道友的工具类排除了周末,离我的需求很接近了,只需要再排除节假日。说到节假日,就得想到调休补班日,假期的时间应该减掉,那么补班日的时间就应该加回来。

初版:

package com.xxxx.xxx.xxxx.utils;

import lombok.Getter;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * 工作时间计算
 * @author tkai
 * @description: 非常 niubility 的处理时间计算,排除了节假日(含调休),以及午休时间。
 * @date 2023/8/10 10:04
 */
public class WorkTimeCalculate {
    /**
     * 上班时间
     */
    private static final LocalTime WORKING_START_TIME = LocalTime.of(9, 0);
    /**
     * 下班时间
     */
    private static final LocalTime WORKING_END_TIME = LocalTime.of(17, 0);
    /**
     * 午休开始时间
     */
    private static final LocalTime NOON_BREAK_START_TIME = LocalTime.of(12, 0);
    /**
     * 午休结束时间
     */
    private static final LocalTime NOON_BREAK_END_TIME = LocalTime.of(13, 0);
    /**
     * 调休日:非周末但是休息的日期列表
     */
    @Getter
    private static final List<LocalDate> holidays = new ArrayList<>();
    /**
     * 补班日:周末但是上班的日期
     */
    @Getter
    private static final List<LocalDate> workdays = new ArrayList();

    //初始化调休日和补班日   后面可考虑从数据库查
    static {
        //调休日
        holidays.add(LocalDate.of(2022,1,3));
        holidays.add(LocalDate.of(2022,1,31));
        holidays.add(LocalDate.of(2022,2,1));
        holidays.add(LocalDate.of(2022,2,2));
        holidays.add(LocalDate.of(2022,2,3));
        holidays.add(LocalDate.of(2022,2,4));
        holidays.add(LocalDate.of(2022,4,4));
        holidays.add(LocalDate.of(2022,4,5));
        holidays.add(LocalDate.of(2022,5,2));
        holidays.add(LocalDate.of(2022,5,3));
        holidays.add(LocalDate.of(2022,5,4));
        holidays.add(LocalDate.of(2022,6,3));
        holidays.add(LocalDate.of(2022,9,12));
        holidays.add(LocalDate.of(2022,10,3));
        holidays.add(LocalDate.of(2022,10,4));
        holidays.add(LocalDate.of(2022,10,5));
        holidays.add(LocalDate.of(2022,10,6));
        holidays.add(LocalDate.of(2022,10,7));

        holidays.add(LocalDate.of(2023,1,2));
        holidays.add(LocalDate.of(2023,1,23));
        holidays.add(LocalDate.of(2023,1,24));
        holidays.add(LocalDate.of(2023,1,25));
        holidays.add(LocalDate.of(2023,1,26));
        holidays.add(LocalDate.of(2023,1,27));
        holidays.add(LocalDate.of(2023,4,5));
        holidays.add(LocalDate.of(2023,5,1));
        holidays.add(LocalDate.of(2023,5,2));
        holidays.add(LocalDate.of(2023,5,3));
        holidays.add(LocalDate.of(2023,6,22));
        holidays.add(LocalDate.of(2023,6,23));
        holidays.add(LocalDate.of(2023,9,29));
        holidays.add(LocalDate.of(2023,10,2));
        holidays.add(LocalDate.of(2023,10,3));
        holidays.add(LocalDate.of(2023,10,4));
        holidays.add(LocalDate.of(2023,10,5));
        holidays.add(LocalDate.of(2023,10,6));
        //补班日
        workdays.add(LocalDate.of(2022,1,29));
        workdays.add(LocalDate.of(2022,1,30));
        workdays.add(LocalDate.of(2022,4,2));
        workdays.add(LocalDate.of(2022,4,24));
        workdays.add(LocalDate.of(2022,5,7));
        workdays.add(LocalDate.of(2022,10,8));
        workdays.add(LocalDate.of(2022,10,9));

        workdays.add(LocalDate.of(2023,1,28));
        workdays.add(LocalDate.of(2023,1,29));
        workdays.add(LocalDate.of(2023,4,23));
        workdays.add(LocalDate.of(2023,5,6));
        workdays.add(LocalDate.of(2023,6,25));
        workdays.add(LocalDate.of(2023,10,7));
        workdays.add(LocalDate.of(2023,10,8));
    }



    public static Integer getWorkTime(LocalDateTime startDateTime, LocalDateTime endDateTime) {
        if (startDateTime.compareTo(endDateTime) > 0) {
            throw new RuntimeException("参数错误,开始时间大于结束时间。");
        }
        int diff = 0;

        while (true) {
            //休息日   开始时间后移至第二天的开始工作时间
            while (needPass(startDateTime)) {
                startDateTime = LocalDateTime.of(startDateTime.toLocalDate(), WORKING_START_TIME).plusDays(1);
            }

            //休息日   结束时间后移至第二天的开始工作时间
            while (needPass(endDateTime)) {
                endDateTime = LocalDateTime.of(endDateTime.toLocalDate(), WORKING_START_TIME).plusDays(1);
            }

            // 跨天处理
            if (startDateTime.getDayOfYear() == endDateTime.getDayOfYear()) {
                int diffSecond = getDiffSecond(startDateTime, endDateTime);
                diff = diffSecond + diff;
                break;
            }
            int diffSecond = getDiffSecond(startDateTime, LocalDateTime.of(startDateTime.toLocalDate(), WORKING_END_TIME));
            diff = diffSecond + diff;
            startDateTime = LocalDateTime.of(startDateTime.toLocalDate(), WORKING_START_TIME).plusDays(1);
        }
        System.out.println("DIFF(hours): " + Double.valueOf(diff) / 3600 +"小时");
        return diff;
    }

    private static int getDiffSecond(LocalDateTime startDateTime, LocalDateTime endDateTime) {
        LocalTime startTime = startDateTime.toLocalTime();
        LocalTime endTime = endDateTime.toLocalTime();
        // diff单位:秒
        int diff = 0;

        // 开始时间切移
        if (startTime.isBefore(WORKING_START_TIME)) {
            startTime = WORKING_START_TIME;
        } else if (startTime.isAfter(NOON_BREAK_START_TIME) && startTime.isBefore(NOON_BREAK_END_TIME)) {
            startTime = NOON_BREAK_START_TIME;
        } else if (startTime.isAfter(WORKING_END_TIME)) {
            startTime = WORKING_END_TIME;
        }
        // 结束时间切移
        if (endTime.isBefore(WORKING_START_TIME)) {
            endTime = WORKING_START_TIME;
        } else if (endTime.isAfter(NOON_BREAK_START_TIME) && endTime.isBefore(NOON_BREAK_END_TIME)) {
            endTime = NOON_BREAK_START_TIME;
        } else if (endTime.isAfter(WORKING_END_TIME)) {
            endTime = WORKING_END_TIME;
        }
        // 午休时间判断处理
        if (startTime.compareTo(NOON_BREAK_START_TIME) <= 0 && endTime.compareTo(NOON_BREAK_END_TIME) >= 0) {
            diff = diff + 60 * 60;
        }
        diff = endTime.toSecondOfDay() - startTime.toSecondOfDay() - diff;

        return diff;
    }


    /**
     * 计算两个时间之间的天数   每天按24小数计算  不考虑上下班  不足一天的部分保留两位小数
     * 特殊情况:开始时间在休息日,则开始时间按下个工作日的00:00:00开始计算
     * 特殊情况:结束时间在休息日,则结束时间按上个工作日的13:59:59开始计算
     * @param startDateTime
     * @param endDateTime
     * @return
     */
    public static BigDecimal getWorkDays(LocalDateTime startDateTime, LocalDateTime endDateTime) {
        if (startDateTime.compareTo(endDateTime) > 0) {
            throw new RuntimeException("参数错误,开始时间大于结束时间。");
        }
        int fullDays = 0;
        long minute = 0;
        //特殊情况:开始时间在休息日,则开始时间按下个工作日的00:00:00开始计算
        if(needPass(startDateTime)){
            startDateTime = startDateTime.toLocalDate().atStartOfDay().plusDays(1);
        }
        //特殊情况:结束时间在休息日,则结束时间按上个工作日的13:59:59开始计算
        if(needPass(endDateTime)){
            endDateTime = endDateTime.toLocalDate().atStartOfDay();
        }

        while (startDateTime.compareTo(endDateTime) < 0) {
            while (needPass(startDateTime)) {
                startDateTime = startDateTime.plusDays(1);
            }
            while (needPass(endDateTime)) {
                endDateTime = endDateTime.plusDays(1);
            }
            if (startDateTime.getDayOfYear() == endDateTime.getDayOfYear()) {
                // 按分钟计算
                minute = Duration.between(startDateTime, endDateTime).toMinutes();
                break;
            }

            startDateTime = startDateTime.plusDays(1);
            fullDays += 1;
        }
        //折算不足一天的时间
        BigDecimal oddDay = new BigDecimal(minute).divide(new BigDecimal(24*60),2, RoundingMode.HALF_UP);

        return new BigDecimal(fullDays).add(oddDay);
    }



    /**
     * 当天休息还是工作
     * @param time
     * @return
     */
    private static boolean needPass(LocalDateTime time){
        if(time.getDayOfWeek() == DayOfWeek.SATURDAY||time.getDayOfWeek() == DayOfWeek.SUNDAY){
            if(isWorkday(time)){
                return false;
            }
            return true;
        }else{
            if(isHoliday(time)){
                return true;
            }
            return false;
        }
    }

    /**
     * 是否调休日
     * @param time
     * @return
     */
    private static boolean isHoliday(LocalDateTime time){
        LocalDate date = time.toLocalDate();
        if(holidays.contains(date)){
            return true;
        }
        return false;
    }

    /**
     * 是否补班日
     * @param time
     * @return
     */
    private static boolean isWorkday(LocalDateTime time){
        LocalDate date = time.toLocalDate();
        if(workdays.contains(date)){
            return true;
        }
        return false;
    }


    public static void main(String[] args) {
        WorkTimeCalculate.getWorkTime(LocalDateTime.of(2022, 4, 29, 11, 30, 0),
                LocalDateTime.of(2022, 5, 17, 20, 30, 0));
        System.out.println(WorkTimeCalculate.getWorkDays(LocalDateTime.of(2022, 4, 29, 12, 30, 0),
                LocalDateTime.of(2022, 5, 7, 13, 0, 0)));
    }
}


然而,跟需求方讨论后得知,不需要精确到小时数,同时,时间也不考虑上下班,每天以24小时计算,计算的单位为天,保留2位小时。


好像是简单了很多,于是基于上面的版本,做了一些修改。

大体思路:遍历开始时间到结束时间之间的每一天,判断当天是不是上班,不是就后移至第二天的0点,否则开始时间后移一天,整天数加1,直到开始时间和结束时间为同一天。随后计算相差的小时数,除以24得到零散部分,这部分可能是正数也可能是负数。最后整天数与零散部分相加,就得到保留二位小数的时间差天数啦。


考虑到调休日和补班日每年都会新增,这一部分从数据库读取比较合理。最后做成工具类如下:

package com.xxxx.xxxxx.xxxx.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

/**
 * @author tkai
 * @description: 工作时长计算
 * @date 2023/8/11 15:25
 */
public class WorkTimeUtil {

    /**
     * 计算两个时间之间的天数   每天按24小数计算  不考虑上下班  不足一天的部分保留两位小数
     * 特殊情况:开始时间在休息日,则开始时间按下个工作日的00:00:00开始计算
     * 特殊情况:结束时间在休息日,则结束时间按上个工作日的13:59:59开始计算
     * @param startDateTime
     * @param endDateTime
     * @return
     */
    public static BigDecimal getWorkDays(LocalDateTime startDateTime, LocalDateTime endDateTime,List<LocalDate> holidays,List<LocalDate> workdays) {
        if (startDateTime.compareTo(endDateTime) > 0) {
//            throw new RuntimeException("参数错误,开始时间大于结束时间。");
            return new BigDecimal(0.00);
        }
        int fullDays = 0;
        long minute = 0;
        //特殊情况:开始时间在休息日,则开始时间按下个工作日的00:00:00开始计算
        if(needPass(startDateTime,holidays,workdays)){
            startDateTime = startDateTime.toLocalDate().atStartOfDay().plusDays(1);
        }
        //特殊情况:结束时间在休息日,则结束时间按上个工作日的23:59:59开始计算
        if(needPass(endDateTime,holidays,workdays)){
            endDateTime = endDateTime.toLocalDate().atStartOfDay();
        }

        while (startDateTime.compareTo(endDateTime) < 0) {
            while (needPass(startDateTime,holidays,workdays)) {
                startDateTime = startDateTime.plusDays(1);
            }
            while (needPass(endDateTime,holidays,workdays)) {
                endDateTime = endDateTime.plusDays(1);
            }
            if (startDateTime.getDayOfYear() == endDateTime.getDayOfYear()) {
                // 按分钟计算
                minute = Duration.between(startDateTime, endDateTime).toMinutes();
                break;
            }

            startDateTime = startDateTime.plusDays(1);
            fullDays += 1;
        }
        //折算不足一天的时间
        BigDecimal oddDay = new BigDecimal(minute).divide(new BigDecimal(24*60),2, RoundingMode.HALF_UP);

        return new BigDecimal(fullDays).add(oddDay);
    }



    /**
     * 当天休息还是工作
     * @param time
     * @return
     */
    private static boolean needPass(LocalDateTime time,List<LocalDate> holidays,List<LocalDate> workdays){
        if(time.getDayOfWeek() == DayOfWeek.SATURDAY||time.getDayOfWeek() == DayOfWeek.SUNDAY){
            if(isWorkday(time,workdays)){
                return false;
            }
            return true;
        }else{
            if(isHoliday(time,holidays)){
                return true;
            }
            return false;
        }
    }

    /**
     * 是否调休日
     * @param time
     * @return
     */
    private static boolean isHoliday(LocalDateTime time,List<LocalDate> holidays){
        LocalDate date = time.toLocalDate();
        if(holidays.contains(date)){
            return true;
        }
        return false;
    }

    /**
     * 是否补班日
     * @param time
     * @return
     */
    private static boolean isWorkday(LocalDateTime time, List<LocalDate> workdays){
        LocalDate date = time.toLocalDate();
        if(workdays.contains(date)){
            return true;
        }
        return false;
    }



}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值