计算预计工作完成时间

文章目录

计算预计完成时间工具类:WorkdayHolidayUtils

这个Java工具类,WorkdayHolidayUtils,提供了一个方便的方式来计算预计的工作完成时间,它考虑到了工作日、非工作日(如周末和法定假日)以及午休时间。这是非常实用的工具类,特别是对于需要考虑具体工作日和工作时间的项目管理和进度跟踪。

java 复制代码
@Slf4j
public class WorkdayHolidayUtils {
  //...
}

如何使用这个工具类?

WorkdayHolidayUtils工具类提供了一个方法getExpectedCompletionDate来计算预计的工作完成时间。此方法需要两个参数:开始日期时间和预计的工作天数。

java 复制代码
LocalDateTime startDateTime = LocalDateTime.of(2023, 7, 28, 9, 30, 0);
int expectedDays = 4;
LocalDateTime expectedCompletionDate = WorkdayHolidayUtils.getExpectedCompletionDate(startDateTime, expectedDays);
// 预计完成时间:2023-08-03T09:30
System.out.println("预计完成时间:" + expectedCompletionDate);

在这个例子中,开始日期时间是2023-07-28 09:30,预计的工作天数是4天。然后,这个方法将返回预计的工作完成时间 2023-08-03 09:30

工具类如何处理工作日和非工作日?

WorkdayHolidayUtils工具类定义了工作日和非工作日,这包括节假日和补班日。这是通过两个Set来存储的:HOLIDAYS和MAKE_UP_SHIFTS。

  • HOLIDAYS 是一个包含所有非工作日日期的Set。
  • MAKE_UP_SHIFTS 是一个包含所有补班日日期的Set。

当计算预计完成时间时,这个工具类将检查每一天是否是工作日。如果一天是周末或者在HOLIDAYS Set中,那么它就不是工作日,除非它在MAKE_UP_SHIFTS Set中。

工具类如何处理非工作时间?

工具类 WorkdayHolidayUtils 在处理非工作时间(包括早上8:30之前,午休时间,和下午17:30之后)时,有一个明确的策略:非工作时间不计入工作进度。

具体来说,在计算预计完成时间的过程中:

  • 如果开始时间在早上8:30之前,工具类会将开始时间自动调整到当天的早上8:30;
  • 如果开始时间在午休时间,工具类会将开始时间自动调整到午休结束后的下午12:30;
  • 如果开始时间在下午17:30之后,工具类会将开始时间自动调整到第二天的早上8:30。

此外,工具类会确保预计完成时间不会落在非工作时间。例如,如果预计完成时间原本是下午17:40,那么工具类会自动将预计完成时间推迟到第二天早上8:40。这样,预计完成时间总是会落在工作时间,而非工作时间不会被计入工作进度。

通过这种策略,工具类能够准确地计算出基于实际工作时间的预计完成时间,而不会受到非工作时间的影响。

代码示例

java 复制代码
package com.wms.common.utils.date;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.wms.common.utils.StringUtils;
import com.wms.common.utils.http.HttpUtils;
import lombok.extern.slf4j.Slf4j;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;

/**
 * Description: [计算预计工作完成时间工具类]
 *
 * @version 1.0
 * @author: wq
 * @date: 2023/4/28 18:17
 */
@Slf4j
public class WorkdayHolidayUtils {

    /**
     * 上午工作开始时间
     */
    private static final LocalTime MORNING_WORK_START_TIME = LocalTime.of(8, 30);
    /**
     * 上午工作结束时间
     */
    private static final LocalTime MORNING_WORK_END_TIME = LocalTime.of(11, 30);
    /**
     * 下午工作开始时间
     */
    private static final LocalTime AFTERNOON_WORK_START_TIME = LocalTime.of(12, 30);
    /**
     * 下午工作结束时间
     */
    private static final LocalTime AFTERNOON_WORK_END_TIME = LocalTime.of(17, 30);

    /**
     * 每天的工作分钟数
     */
    private static final int WORK_MINUTES_PER_DAY = 8 * 60;

    /**
     * 定义节假日和休息日
     */
    private static final Set<LocalDate> HOLIDAYS = new HashSet<>();

    /**
     * 定义补班日
     */
    private static final Set<LocalDate> MAKE_UP_SHIFTS = new HashSet<>();

    /**
     * 构造函数,接收节假日和休息日的列表
     *
     * @param holidayList 节假日和休息日的列表
     */
    public WorkdayHolidayUtils(List<Map<String, Object>> holidayList) {


        holidayList.forEach(map -> {
            // 获取节假日标志和节假日日期字符串
            String isHoliday = map.get("isHoliday").toString();
            Date holidayDate = (Date) map.get("holidayDate");
            LocalDate holidayLocalDate = DateUtils.toLocalDate(holidayDate);

            // 根据节假日标志将日期添加到对应的列表中
            if ("0".equals(isHoliday)) {
                // 调休工作日列表
                HOLIDAYS.add(holidayLocalDate);
            } else if ("1".equals(isHoliday)) {
                // 法定调休日列表
                MAKE_UP_SHIFTS.add(holidayLocalDate);
            }
        });
    }

    /**
     * 计算预计完成时间。
     * <p>
     * 1. 将预计的工作天数转换为分钟数
     * 2. 检查开始日期是否为工作日,如果是,则计算第一天的剩余工作时间,并从预计的工作分钟数中减去
     * 3. 循环直到所有预计的分钟数都被计算完,期间需要检查新的日期是否是工作日,如果是则从预计的工作分钟数中减去一整天的工作时间
     * 4. 调用 adjustCompletionDateTime 方法根据工作时间调整预计完成的日期和时间
     * 5. 最后检查预计完成日期是否是非工作日,如果是,则将预计完成日期推迟到下一个工作日
     *
     * @param startDateTime 开始日期和时间
     * @param expectedDays  预计需要工作的天数
     * @return 预计完成的日期和时间
     */
    public static LocalDateTime getExpectedCompletionDate(LocalDateTime startDateTime, int expectedDays) {
        // 将预计的工作天数转换为分钟数
        int expectedMinutes = expectedDays * WORK_MINUTES_PER_DAY;
        LocalDateTime currentDateTime = startDateTime;

        // 检查开始日期是否为工作日
        if (isWorkingDay(currentDateTime.toLocalDate())) {
            // 计算第一天的剩余工作时间
            int remainingMinutesFirstDay = calculateRemainingMinutesFirstDay(currentDateTime);
            // 如果第一天有剩余的工作时间,将它从预计的工作分钟数中减去
            if (remainingMinutesFirstDay > 0) {
                expectedMinutes -= Math.min(expectedMinutes, remainingMinutesFirstDay);
            }
        }

        // 延迟分钟数
        int delayMinutes = 0;

        // 计算剩余的工作天数
        while (expectedMinutes > 0) {
            // 将当前日期增加一天,并设定时间为上午8:30开始工作
            currentDateTime = currentDateTime.plusDays(1);
            // 检查新的日期是否是工作日
            if (isWorkingDay(currentDateTime.toLocalDate())) {
                // 是工作日,设定时间为上午8:30开始工作
                currentDateTime = currentDateTime.with(MORNING_WORK_START_TIME);
                // 如果是工作日,将一整天的工作时间从预计的工作分钟数中减去
                delayMinutes = Math.min(expectedMinutes, WORK_MINUTES_PER_DAY);
                expectedMinutes -= delayMinutes;
            }
        }

        // 根据工作时间调整预计完成的日期和时间
        LocalDateTime expectedCompletionDateTime = adjustCompletionDateTime(currentDateTime, delayMinutes);

        // 如果预计完成日期是非工作日,则将预计完成日期推迟到下一个工作日
        while (!isWorkingDay(expectedCompletionDateTime.toLocalDate())) {
            expectedCompletionDateTime = expectedCompletionDateTime.plusDays(1);
        }

        // 返回预计完成的日期和时间
        return expectedCompletionDateTime;
    }

    /**
     * 计算第一天的剩余工作分钟数。
     * 假设工作时间是从上午8:30到11:30,然后下午12:30到17:30。
     *
     * @param currentDateTime 当前日期和时间
     * @return 剩余的工作分钟数
     */
    private static int calculateRemainingMinutesFirstDay(LocalDateTime currentDateTime) {
        // 获取当前时间
        LocalTime currentTime = currentDateTime.toLocalTime();

        // 如果当前时间在工作开始时间之前,则将工作开始时间作为当前时间
        if (currentTime.isBefore(MORNING_WORK_START_TIME)) {
            currentTime = MORNING_WORK_START_TIME;
        }

        // 如果当前时间在上午的工作结束时间之前
        if (currentTime.isBefore(MORNING_WORK_END_TIME)) {
            // 计算从当前时间到上午工作结束时间的分钟数
            // 再加上下午的工作分钟数
            return (int) DateUtils.between(ChronoUnit.MINUTES, currentTime, MORNING_WORK_END_TIME)
                    + (int) DateUtils.between(ChronoUnit.MINUTES, AFTERNOON_WORK_START_TIME, AFTERNOON_WORK_END_TIME);
        }
        // 如果当前时间在午休时间之前
        else if (currentTime.isBefore(AFTERNOON_WORK_START_TIME)) {
            // 直接返回下午的工作分钟数
            return (int) DateUtils.between(ChronoUnit.MINUTES, AFTERNOON_WORK_START_TIME, AFTERNOON_WORK_END_TIME);
        }
        // 如果当前时间在下午的工作结束时间之前
        else if (currentTime.isBefore(AFTERNOON_WORK_END_TIME)) {
            // 计算从当前时间到下午工作结束时间的分钟数
            return (int) DateUtils.between(ChronoUnit.MINUTES, currentTime, AFTERNOON_WORK_END_TIME);
        }

        // 如果当前时间已经超过了当天的工作时间,那么剩余的工作分钟数就是0
        return 0;
    }

    /**
     * 根据工作时间调整预计完成日期和时间。
     * 假设工作时间是从上午8:30到11:30,然后下午12:30到17:30。
     *
     * @param currentDateTime 当前日期和时间
     * @param delayMinutes    需要延迟的分钟数
     * @return 调整后的预计完成日期和时间
     */
    private static LocalDateTime adjustCompletionDateTime(LocalDateTime currentDateTime, int delayMinutes) {
        // 首先将延迟的分钟数加到当前日期和时间上,得到预计完成日期和时间
        LocalDateTime expectedCompletionDateTime = currentDateTime.plusMinutes(delayMinutes);

        // 如果预计完成时间在午休之前(即上午工作结束时间之前)
        if (expectedCompletionDateTime.toLocalTime().isBefore(MORNING_WORK_END_TIME)) {
            // 返回调整后的预计完成日期和时间
            return expectedCompletionDateTime;
        }
        // 如果预计完成时间在午休时间段(即上午工作结束时间之后,下午工作开始时间之前)
        else if (DateUtils.isWithinRange(expectedCompletionDateTime.toLocalTime(), MORNING_WORK_END_TIME, AFTERNOON_WORK_START_TIME)) {
            // 需要向后推迟,跳过午休时间
            expectedCompletionDateTime = expectedCompletionDateTime.plusMinutes((int) DateUtils.between(ChronoUnit.MINUTES, MORNING_WORK_END_TIME, AFTERNOON_WORK_START_TIME));
        }
        // 如果预计完成时间在下午工作时间段(即下午工作开始时间之后,下午工作结束时间之前)
        else if (DateUtils.isWithinRange(expectedCompletionDateTime.toLocalTime(), AFTERNOON_WORK_START_TIME, AFTERNOON_WORK_END_TIME)) {
            // 需要向后推迟,跳过午休时间
            expectedCompletionDateTime = expectedCompletionDateTime.plusMinutes((int) DateUtils.between(ChronoUnit.MINUTES, MORNING_WORK_END_TIME, AFTERNOON_WORK_START_TIME));
        }
        // 如果预计完成时间超过下班时间(即下午工作结束时间之后)
        else if (expectedCompletionDateTime.toLocalTime().isAfter(AFTERNOON_WORK_END_TIME)) {
            // 计算超过的分钟数
            long minutesAfterWork = DateUtils.between(ChronoUnit.MINUTES, AFTERNOON_WORK_END_TIME, expectedCompletionDateTime.toLocalTime());
            // 需要向后推迟到下一个工作日的开始时间,并加上超过的分钟数
            expectedCompletionDateTime = expectedCompletionDateTime.plusDays(1).with(MORNING_WORK_START_TIME).plusMinutes(minutesAfterWork);
        }

        // 返回调整后的预计完成日期和时间
        return expectedCompletionDateTime;

        /*
        // 工作日的总工作分钟数
        int minutesPerDay = WORK_MINUTES_PER_DAY;

        // 计算需要工作的完整天数
        int days = delayMinutes / minutesPerDay;
        // 计算最后一天需要工作的分钟数
        int remainingMinutes = delayMinutes % minutesPerDay;

        // 将需要工作的完整天数加到当前日期和时间上
        currentDateTime = currentDateTime.plusDays(days);

        // 上午工作时间
        int morningWorkMinutes = (int) DateUtils.between(ChronoUnit.MINUTES, MORNING_WORK_START_TIME, MORNING_WORK_END_TIME);

        // 判断最后一天需要工作的分钟数是否在上午工作时间内
        if (remainingMinutes <= morningWorkMinutes) {
            // 如果在上午工作时间内,则将这些分钟加到上午工作开始时间上
            return currentDateTime.with(MORNING_WORK_START_TIME).plusMinutes(remainingMinutes);
        } else {
            // 如果超过了上午的工作时间,则需要将它加到下午工作开始时间上
            // 首先,需要将上午工作时间从剩余的分钟数中减去
            remainingMinutes -= morningWorkMinutes;
            // 然后,将剩余的分钟数加到下午工作开始时间上
            return currentDateTime.with(AFTERNOON_WORK_START_TIME).plusMinutes(remainingMinutes);
        }
        */
    }

    /**
     * 判断指定日期是否是工作日。
     * 我们假设一个工作日要么不是周末,要么在补班日列表中。
     * 如果一个日期是周末,但又在补班日列表中,那么它仍然是工作日。
     * 如果一个日期是工作日,但又在假期列表中,那么它就不再是工作日。
     *
     * @param date 要判断的日期
     * @return 如果指定日期是工作日,返回true;否则返回false。
     */
    private static boolean isWorkingDay(LocalDate date) {
        // 如果指定日期不是周末并且不在假期列表中,或者指定日期在补班日列表中,那么它就是工作日
        return (!isWeekend(date) && !HOLIDAYS.contains(date)) || MAKE_UP_SHIFTS.contains(date);
    }

    /**
     * 判断指定日期是否是周末。
     *
     * @param date 要判断的日期
     * @return 如果指定日期是周六或周日,返回true;否则返回false。
     */
    private static boolean isWeekend(LocalDate date) {
        // 如果指定日期是周六或周日,返回true;否则返回false
        return date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY;
    }

    /**
     * Description: 获取指定年份的节假日数据(在外网调用此方法)
     *
     * @param year 指定年份
     * @return 节假日数据的Map
     */
    private static Map<String, Object> getHoliday(int year) {
        String url;
        if (year != 0) {
            url = "http://timor.tech/api/holiday/year/" + year;
        } else {
            // 如果year为0,则获取当前年份
            url = "http://timor.tech/api/holiday/year/";
        }
        // 发送HTTP请求获取JSON数据
        String jsonData = HttpUtils.sendGet(url);
        if (StringUtils.isEmpty(jsonData)) {
            // 如果获取的JSON数据为空,则记录日志并返回空的Map
            log.error("获取节假日数据失败");
            return Collections.emptyMap();
        }
        // 将JSON数据解析为Map
        return JSON.parseObject(jsonData, new TypeReference<Map<String, Object>>() {
        });
    }

    /**
     * Description: 解析获取指定年份的节假日信息
     *
     * @param year 年份
     * @return 节假日信息集合,如果获取失败则返回空集合
     */
    public static Set<Map<String, Object>> parseHolidayDate(int year) {
        //http://timor.tech/api/holiday api文档地址

        // 用LinkedHashSet保证结果集有序
        Set<Map<String, Object>> result = new LinkedHashSet<>();

        // 获取节假日数据
        Map<String, Object> holidayData = getHoliday(year);
        // 判断返回码是否为0,如果不为0说明获取失败,直接返回空集合
        Integer code = (Integer) holidayData.get("code");
        if (code != 0) {
            return Collections.emptySet();
        }
        // 获取节假日数据,遍历处理每个节假日信息
        Map<String, Map<String, Object>> holiday = (Map<String, Map<String, Object>>) holidayData.get("holiday");
        holiday.values().forEach(o -> {
            // 将每个节假日信息转换为指定格式
            Map<String, Object> map = new HashMap<>();
            boolean isHoliday = (boolean) o.get("holiday");
            String HOLIDAYStr = isHoliday ? "0" : "1";

            // 将转换后的信息加入结果集
            map.put("isHoliday", HOLIDAYStr);
            map.put("holidayName", o.get("name"));
            map.put("holidayDate", o.get("date"));
            result.add(map);
        });
        return result;
    }

    /**
     * 计算预计完成时间 【只计算天数/*】
     *
     * @param startDateTime 开始日期
     * @param expectedDays  预计天数
     * @return 预计完成日期
     */
    /*
    public static LocalDateTime getExpectedCompletionDate(LocalDateTime startDateTime, int expectedDays) {

        int delay = 1;

        LocalDate startDate = startDateTime.toLocalDate();

        while (delay <= expectedDays) {
            // 将开始日期增加一天
            startDate = startDate.plusDays(1);
            // 判断是否为周末或节假日
            if ((!isWeekend(startDate) && !HOLIDAYS.contains(startDate)) || MAKE_UP_SHIFTS.contains(startDate)) {
                delay++;
            }
        }

        // 将 startDateTime 的日期部分替换为新的 startDate
        return startDateTime.with(startDate);
    }*/
    public static void main(String[] args) {
        // 当前日期
        LocalDateTime startDateTime = LocalDateTime.of(2023, 7, 28, 9, 30, 0);
        // 预计天数
        int expectedDays = 4;

        // 定义节假日和休息日
//        List<Map<String, Object>> holidayList = new ArrayList<>();

//        Set<LocalDate> HOLIDAYS = new HashSet<>();
//        HOLIDAYS.add(LocalDate.of(2023, 6, 22));
//        HOLIDAYS.add(LocalDate.of(2023, 6, 23));
//        HOLIDAYS.add(LocalDate.of(2023, 6, 24));
//        HOLIDAYS.add(LocalDate.of(2023, 6, 21));
//        HOLIDAYS.add(LocalDate.of(2023, 6, 21));

        // 节假日
//        HOLIDAYS.forEach(date -> {
//            Map<String, Object> map = new HashMap<>();
//            map.put("isHoliday", "0");
//            map.put("holidayDate", date);
//            holidayList.add(map);
//        });

        // 补班
//        Map<String, Object> map = new HashMap<>();
//        map.put("isHoliday", "1");
//        map.put("holidayDate", "2023-06-25");
//        holidayList.add(map);
//        MAKE_UP_SHIFTS.add(LocalDate.of(2023, 7, 29));
        // 初始化计算器
//        HolidayUtils calculator = new HolidayUtils(holidayList);

        LocalDateTime expectedCompletionDate = getExpectedCompletionDate(startDateTime, expectedDays);
        System.out.println("预计完成时间:" + expectedCompletionDate);


//        Set<Map<String, Object>> maps = parseHolidayDate(2023);
//        System.out.println(maps);
    }

}

日期工具类

上面的代码示例用到了日期工具类, 特此附上
以下代码中有用到StringUtils工具类, 此类在以下代码中是用来判断一个对象是否为空, 自行替换吧。

java 复制代码
package com.wms.common.utils.date;

import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalUnit;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.wms.common.utils.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

/**
 * 时间工具类
 *
 * @author wq
 */
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
    public static String YYYY = "yyyy";

    public static String YYYY_MM = "yyyy-MM";

    public static String YYYY_MM_DD = "yyyy-MM-dd";

    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";

    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    private static String[] parsePatterns = {
            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};

    /**
     * 获取当前Date型日期
     *
     * @return Date() 当前日期
     */
    public static Date getNowDate() {
        return new Date();
    }

    /**
     * 获取当前日期, 默认格式为yyyy-MM-dd
     *
     * @return String
     */
    public static String getDate() {
        return dateTimeNow(YYYY_MM_DD);
    }

    /**
     * 获取当前时间, 默认格式为yyyy-MM-dd HH:mm:ss
     *
     * @return String
     */
    public static final String getTime() {
        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
    }

    public static final String dateTimeNow() {
        return dateTimeNow(YYYYMMDDHHMMSS);
    }

    public static final String dateTimeNow(final String format) {
        return parseDateToStr(format, new Date());
    }

    public static final String dateTime(final Date date) {
        return parseDateToStr(YYYY_MM_DD, date);
    }

    /**
     * Description: TODO Date 转 String
     *
     * @param format
     * @param date
     * @author: wangqiang
     * @return: java.lang.String
     * @date: 2022/9/14 18:25
     */
    public static final String parseDateToStr(final String format, final Date date) {
        return new SimpleDateFormat(format).format(date);
    }

    /**
     * 字符串 转 Date
     *
     * @param format 日期格式
     * @param ts     数据
     * @return
     */
    public static final Date parseStrToDate(final String format, final String ts) {
        try {
            return new SimpleDateFormat(format).parse(ts);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 日期路径 即年/月/日 如2018/08/08
     */
    public static final String datePath() {
        Date now = new Date();
        return DateFormatUtils.format(now, "yyyy/MM/dd");
    }

    /**
     * 日期路径 即年/月/日 如20180808
     */
    public static final String dateTime() {
        Date now = new Date();
        return DateFormatUtils.format(now, "yyyyMMdd");
    }

    /**
     * 日期型字符串转化为日期 格式
     */
    public static Date parseDate(Object str) {
        if (str == null) {
            return null;
        }
        try {
            return parseDate(str.toString(), parsePatterns);
        } catch (ParseException e) {
            return null;
        }
    }

    /**
     * 获取服务器启动时间
     */
    public static Date getServerStartDate() {
        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
        return new Date(time);
    }

    /**
     * 计算相差天数
     */
    public static int differentDaysByMillisecond(Date date1, Date date2) {
        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
    }

    /**
     * Description: TODO 计算两个时间差
     *
     * @param startDate
     * @param endDate
     * @param str       返回的数据为:dhm-[天,小时,分钟]、hm-[小时,分钟]
     * @author: wangqiang
     * @return: java.lang.String
     * @date: 2022/7/21 15:06
     */
    public static String getDatePoor(Date startDate, Date endDate, String str) {
        long nd = 1000 * 24 * 60 * 60;
        long nh = 1000 * 60 * 60;
        long nm = 1000 * 60;
        // long ns = 1000;
        // 获得两个时间的毫秒时间差异
        long diff = endDate.getTime() - startDate.getTime();
        // 计算差多少天
        long day = diff / nd;
        // 计算差多少小时
        long hour = diff % nd / nh;
        // 计算差多少分钟
        long min = diff % nd % nh / nm;
        // 计算差多少秒//输出结果
        // long sec = diff % nd % nh % nm / ns;
        if (str.equalsIgnoreCase("hm")) {
            return hour + "小时" + min + "分钟";
        }
        return day + "天" + hour + "小时" + min + "分钟";
    }

    /**
     * 将毫秒数转换为天、小时、分钟
     *
     * @param millis 毫秒数
     * @return 返回包含天、小时、分钟的字符串,如 "2天5小时30分钟"
     */
    public static String millisToDHM(long millis) {
        // 转换为天数
        long days = TimeUnit.MILLISECONDS.toDays(millis);
        // 减去天数后剩余的毫秒数
        long hoursAndMinutesMillis = millis - TimeUnit.DAYS.toMillis(days);
        // 转换为小时数
        long hours = TimeUnit.MILLISECONDS.toHours(hoursAndMinutesMillis);
        // 减去小时数后剩余的毫秒数
        long minutesAndSecondsMillis = hoursAndMinutesMillis - TimeUnit.HOURS.toMillis(hours);
        // 转换为分钟数
        long minutes = TimeUnit.MILLISECONDS.toMinutes(minutesAndSecondsMillis);
        // 拼接成字符串
        StringBuilder sb = new StringBuilder();
        if (days > 0) {
            sb.append(days).append("天");
        }
        if (hours > 0) {
            sb.append(hours).append("小时");
        }
        if (minutes > 0) {
            sb.append(minutes).append("分钟");
        }
        // 毫秒数小于1分钟,返回最少1分钟
        return sb.length() > 0 ? sb.toString() : "1分钟";
    }

    /**
     * Description: TODO 计算2个时间相差的天数、小时、分钟、秒
     *
     * @param startTime 开始时间
     * @param endTime   截止时间
     * @param str       返回的数据为:day-天、hour-小时、min-分钟、second-秒 、millisecond-毫秒
     * @author: wangqiang
     * @return: java.lang.Long
     * @date: 2022/7/15 17:57
     */
    /*public static Long dateDiff(Date startTime, Date endTime, String str) {
        // 按照传入的格式生成一个simpledateformate对象
        //SimpleDateFormat sd = new SimpleDateFormat(format);
        // 一天的毫秒数
        long nd = 1000 * 24 * 60 * 60;
        // 一小时的毫秒数
        long nh = 1000 * 60 * 60;
        // 一分钟的毫秒数
        long nm = 1000 * 60;
        // 一秒钟的毫秒数
        long ns = 1000;
        long diff;
        long day = 0;
        long hour = 0;
        long min = 0;
        long second = 0;
        // 获得两个时间的毫秒时间差异
        //try {
        diff = endTime.getTime() - startTime.getTime();
        // 计算差多少天
        day = diff / nd;
        // 计算差多少小时
        hour = diff / nh;
        // 计算差多少分钟
        min = diff / nm;
        // 计算差多少秒
        second = diff / ns;
        // 输出结果
//        System.out.println("时间相差:" + day + "天" +
//                (hour - day * 24) + "小时"
//                + (min - day * 24 * 60) + "分钟" +
//                second + "秒。");
        *//*System.out.println("hour=" + hour + ",min=" + min);*//*
        //} catch (ParseException e) {
        // TODO Auto-generated catch block
        //    e.printStackTrace();
        //}
        if (str.equalsIgnoreCase("day")) {
            return day;
        } else if (str.equalsIgnoreCase("hour")) {
            return hour;
        } else if (str.equalsIgnoreCase("min")) {
            return min;
        } else if (str.equalsIgnoreCase("second"))  {
            return second;
        } else {
            return diff;
        }
    }*/

    /**
     * 计算两个日期之间的差距,并返回相应时间单位(天、小时、分钟、秒)的差距数值。
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @param str       时间单位,可选值包括:day、hour、min、second、millisecond
     * @return 返回两个日期之间相差的差距数值,根据参数str确定返回的时间单位
     */
    public static long dateDiff(Date startTime, Date endTime, String str) {
        // 计算两个日期之间的毫秒数差距
        long diff = endTime.getTime() - startTime.getTime();

        // 根据参数str的值来决定返回的时间单位
        switch (str.toLowerCase()) {
            // 如果参数str为day,则返回相差的天数
            case "day":
                return TimeUnit.MILLISECONDS.toDays(diff);
            // 如果参数str为hour,则返回相差的小时数
            case "hour":
                return TimeUnit.MILLISECONDS.toHours(diff);
            // 如果参数str为min,则返回相差的分钟数
            case "min":
                return TimeUnit.MILLISECONDS.toMinutes(diff);
            // 如果参数str为second,则返回相差的秒数
            case "second":
                return TimeUnit.MILLISECONDS.toSeconds(diff);
            // 如果参数str为millisecond,则返回相差的秒数
            case "millisecond":
                return diff;
            // 如果参数str为其他值,则返回空
            default:
                return 0;
        }
    }


    /**
     * 增加 LocalDateTime ==> Date
     */
    public static Date toDate(LocalDateTime temporalAccessor) {
        ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
        return Date.from(zdt.toInstant());
    }

    /**
     * 增加 LocalDate ==> Date
     */
    public static Date toDate(LocalDate temporalAccessor) {
        LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
        return Date.from(zdt.toInstant());
    }

    /**
     * Date对象转换为LocalDate对象
     *
     * @param date 要转换的Date对象
     * @return 转换后的LocalDate对象
     */
    public static LocalDate toLocalDate(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * Description: TODO LocalDateTime 转 String
     *
     * @param dateTime
     * @author: wangqiang
     * @return: java.lang.String
     * @date: 2022/8/10 10:29
     */
    public static String localDateTimeToString(LocalDateTime dateTime) {
        DateTimeFormatter df = DateTimeFormatter.ofPattern(DateUtils.YYYY_MM_DD_HH_MM_SS);
        return df.format(dateTime);
    }

    /**
     * Description: TODO Date 转 LocalDateTime
     *
     * @param date
     * @author: wangqiang
     * @return: java.time.LocalDateTime
     * @date: 2022/8/10 12:45
     */
    public static LocalDateTime dateToLocalDateTime(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    /**
     * Description: TODO    日期减去xx(秒/分钟/小时/天/月/年 等等)
     *
     * @param num      天数 / 月数 等
     * @param dateType 日期类型
     * @author: wangqiang
     * @return: java.lang.String
     * @date: 2022/8/10 10:31
     */
    public static String minusDateToS(Date date, long num, TemporalUnit dateType) {
        LocalDateTime localDateTime = dateToLocalDateTime(date);
        LocalDateTime minus = localDateTime.minus(num, dateType);
        return localDateTimeToString(minus);
    }

    /**
     * Description: TODO 日期增加xx(秒/分钟/小时/天/月/年 等等)
     *
     * @param date     指定日期
     * @param num      天数 / 月数 等
     * @param dateType 日期类型
     * @author: wangqiang
     * @return: java.lang.String
     * @date: 2022/8/10 12:54
     */
    public static String plusDateToS(Date date, long num, TemporalUnit dateType) {
        LocalDateTime localDateTime = dateToLocalDateTime(date);
        LocalDateTime plus = localDateTime.plus(num, dateType);
        return localDateTimeToString(plus);
    }

    /**
     * Description: TODO 日期增加xx(秒/分钟/小时/天/月/年 等等)
     *
     * @param date     指定日期
     * @param num      天数 / 月数 等
     * @param dateType 日期类型
     * @author: wangqiang
     * @return: java.util.Date
     * @date: 2022/8/10 12:54
     */
    public static Date plusDateToD(Date date, long num, TemporalUnit dateType) {
        if (StringUtils.isNull(date)) {
            return null;
        }
        LocalDateTime localDateTime = dateToLocalDateTime(date);
        LocalDateTime plus = localDateTime.plus(num, dateType);
        return toDate(plus);
    }

    /**
     * Description: TODO 日期增加xx(秒/分钟/小时/天/月/年 等等)
     *
     * @param date     指定日期
     * @param num      天数 / 月数 等
     * @param dateType 日期类型
     * @author: wangqiang
     * @return: LocalDateTime
     * @date: 2022/8/10 12:54
     */
    public static LocalDateTime plusDateToLD(Date date, long num, TemporalUnit dateType) {
        if (StringUtils.isNull(date)) {
            return null;
        }
        LocalDateTime localDateTime = dateToLocalDateTime(date);
        LocalDateTime plus = localDateTime.plus(num, dateType);
        return plus;
    }

    /**
     * Description: TODO 获取指定日期区间内的中间刻度集合
     *
     * @param beginTime 起始时间
     * @param endTime   结束日期
     * @param timeScale 间隔分钟数
     * @author: wangqiang
     * @return: java.util.List<java.util.Date>
     * @date: 2022/9/8 14:22
     */
    public static List<Date> getTimeScaleList(Date beginTime, Date endTime, int timeScale) {

        List<Date> result = new ArrayList<>();
        /*
            Date.compareTo()
            如果两个日期相等,则返回值为0。
            如果Date在date参数之后,则返回值大于0。
            如果Date在date参数之前,则返回值小于0。
        */
        while (beginTime.compareTo(endTime) <= 0) {
            result.add(beginTime);
            //日期加指定分钟数
            beginTime = addMin(beginTime, timeScale);
        }
        return result;
    }

    /**
     * Description: TODO 给日期添加指定分钟数
     *
     * @param start  起始时间
     * @param offset 间隔分钟数
     * @author: wangqiang
     * @return: java.util.Date
     * @date: 2022/9/8 14:25
     */
    public static Date addMin(Date start, int offset) {
        Calendar c = Calendar.getInstance();
        c.setTime(start);
        c.add(Calendar.MINUTE, offset);
        return c.getTime();
    }

    /**
     * Description: TODO 获取指定年月日时分秒
     *
     * @param date   年月日
     * @param hour   小时
     * @param minute 分钟
     * @param second 秒数
     * @author: wangqiang
     * @return: java.util.Date
     * @date: 2022/9/8 14:25
     */
    public static Date getyMdHms(LocalDate date, Integer hour, Integer minute, Integer second) {
        LocalDateTime localDateTime = LocalDateTime.of(date, LocalTime.of(hour, minute, second));
        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
        return Date.from(zdt.toInstant());
    }

    /**
     * 将分钟数转换为天数。
     *
     * @param minutes 要转换的分钟数
     * @return 转换后的天数
     */
    public static double minToDays(long minutes) {
        // 一天的总分钟数
        final double MINUTES_PER_DAY = 24 * 60.0;
        // 计算天数
        double days = minutes / MINUTES_PER_DAY;
        // 返回结果
        return days;
    }

    /**
     * 将 LocalDate 转换为毫秒
     */
    public static long localDateToMilliseconds(LocalDate localDate) {
        return localDate.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
    }

    /**
     * 将 LocalDate 转换为秒
     */
    public static long localDateToSeconds(LocalDate localDate) {
        return localDate.atStartOfDay().toInstant(ZoneOffset.UTC).getEpochSecond();
    }

    /**
     * 检查给定时间是否在指定的时间范围内 (包含)
     *
     * @param time      要检查的时间
     * @param beginTime 开始时间 (包含)
     * @param endTime   结束时间 (包含)
     * @return 如果给定时间在开始时间和结束时间之间,返回 true,否则返回 false
     */
    public static boolean isWithinRange(LocalTime time, LocalTime beginTime, LocalTime endTime) {
        return !time.isBefore(beginTime) && !time.isAfter(endTime);
    }

    /**
     * 检查给定时间是否在指定的时间范围内 (不包含)
     *
     * @param time      要检查的时间
     * @param beginTime 开始时间 (不包含)
     * @param endTime   结束时间 (不包含)
     * @return 如果给定时间在开始时间和结束时间之间,返回 true,否则返回 false
     */
    public static boolean isWithinRangeExclusive(LocalTime time, LocalTime beginTime, LocalTime endTime) {
        return time.isAfter(beginTime) && time.isBefore(endTime);
    }

    /**
     * 计算两个时间之间的时间差
     *
     * @param unit  时间单位,可以是 ChronoUnit.DAYS、ChronoUnit.HOURS、ChronoUnit.MINUTES 等
     * @param start 开始时间
     * @param end   结束时间
     * @return 两个时间之间的时间差,根据 unit 的不同返回不同单位的时间差
     */
    public static long between(ChronoUnit unit, Temporal start, Temporal end) {
        return unit.between(start, end);
    }

    public static void main(String[] args) throws ParseException {
//        SimpleDateFormat sd = new SimpleDateFormat(DateUtils.YYYY_MM_DD_HH_MM_SS);
//        Long min = DateUtils.dateDiff(sd.parse("2022-08-31 06:00:00"), sd.parse("2022-08-31 10:00:00"), "min");
//        System.out.println(min);
//        int i = differentDaysByMillisecond(sd.parse("2022-08-31 06:00:00"), sd.parse("2022-08-31 10:00:00"));
        String s = DateUtils.minusDateToS(DateUtils.getNowDate(), 1, ChronoUnit.MONTHS);
//        System.out.println(i);

        int i = differentDaysByMillisecond(parseStrToDate(YYYY_MM_DD_HH_MM_SS,"2022-08-31 14:30:00"),parseStrToDate("2022-08-31 15:00:00",YYYY_MM_DD_HH_MM_SS));
//        System.out.println(getDatePoor(sd.parse("2022-08-30 06:15:00"), sd.parse("2022-08-31 10:00:00"), "dhm"));
//        System.out.println(millisToDHM(99900000));


        LocalTime time1 = LocalTime.of(10, 30);
        LocalTime time2 = LocalTime.of(8, 30);
        LocalTime time3 = LocalTime.of(11, 30);

        System.out.println(isWithinRange(time1, time2, time3)); // 输出:true

        LocalTime time4 = LocalTime.of(7, 30);
        System.out.println(isWithinRange(time4, time2, time3)); // 输出:false

        LocalTime time5 = LocalTime.of(12, 30);
        System.out.println(isWithinRange(time5, time2, time3)); // 输出:false

        LocalTime time6 = LocalTime.of(8, 30);
        System.out.println(isWithinRange(time6, time2, time3)); // 输出:false

        LocalTime time7 = LocalTime.of(11, 30);
        System.out.println(isWithinRange(time7, time2, time3)); // 输出:false
    }

}

数据库中的节假日信息表

为了管理节假日和补班日的信息,我们在数据库中创建了一个 holiday 表。表结构如下:

sql 复制代码
-- auto-generated definition
create table holiday
(
    id           bigint auto_increment comment 'ID'
        primary key,
    holiday_date date                   null comment '节假日期',
    holiday_name varchar(10) default '' null comment '节假日名称',
    is_holiday   char        default '' null comment '是否节假日(0节假日 1补班)'
)
    comment '节假日信息表';

此表中,holiday_date 列表示节假日的日期,holiday_name 列表示节假日的名称,is_holiday 列表示该日期是否是节假日(0 表示节假日,1 表示补班)。通过这个表,我们可以方便地管理节假日和补班日的信息。

示例数据

为了方便理解,我们提供了一些示例数据。这些数据是2023年的节假日和补班日信息。如需要其他年份数据, 调用parseHolidayDate()方法获取。保存在 holiday 表中:

sql 复制代码
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (96, '2023-01-01', '元旦', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (97, '2023-01-02', '元旦', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (98, '2023-01-21', '除夕', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (99, '2023-01-22', '初一', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (100, '2023-01-23', '初二', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (101, '2023-01-24', '初三', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (102, '2023-01-25', '初四', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (103, '2023-01-26', '初五', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (104, '2023-01-27', '初六', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (105, '2023-01-28', '春节后补班', '1');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (106, '2023-01-29', '春节后补班', '1');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (107, '2023-04-05', '清明节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (108, '2023-04-23', '劳动节前补班', '1');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (109, '2023-04-29', '劳动节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (110, '2023-04-30', '劳动节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (111, '2023-05-01', '劳动节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (112, '2023-05-02', '劳动节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (113, '2023-05-03', '劳动节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (114, '2023-05-06', '劳动节后补班', '1');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (115, '2023-06-22', '端午节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (116, '2023-06-23', '端午节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (117, '2023-06-24', '端午节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (118, '2023-06-25', '端午节后补班', '1');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (119, '2023-09-29', '中秋节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (120, '2023-09-30', '中秋节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (121, '2023-10-01', '国庆节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (122, '2023-10-02', '国庆节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (123, '2023-10-03', '国庆节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (124, '2023-10-04', '国庆节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (125, '2023-10-05', '国庆节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (126, '2023-10-06', '国庆节', '0');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (127, '2023-10-07', '国庆节后补班', '1');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (128, '2023-10-08', '国庆节后补班', '1');
INSERT INTO `code_safe`.`holiday` (`id`, `holiday_date`, `holiday_name`, `is_holiday`) VALUES (129, '2023-12-31', '元旦', '0');

小结

WorkdayHolidayUtils 是一个非常实用的工具类,它考虑到了工作日、节假日、补班日、双休日和非工作时间。无论是进行项目管理,还是简单地需要考虑具体的工作日和工作时间,这个工具类都将提供非常大的帮助。

相关推荐
亲爱的非洲野猪2 分钟前
Kafka消息积压的多维度解决方案:超越简单扩容的完整策略
java·分布式·中间件·kafka
wfsm4 分钟前
spring事件使用
java·后端·spring
微风粼粼22 分钟前
程序员在线接单
java·jvm·后端·python·eclipse·tomcat·dubbo
缘来是庄26 分钟前
设计模式之中介者模式
java·设计模式·中介者模式
rebel1 小时前
若依框架整合 CXF 实现 WebService 改造流程(后端)
java·后端
代码的余温2 小时前
5种高效解决Maven依赖冲突的方法
java·maven
慕y2742 小时前
Java学习第十六部分——JUnit框架
java·开发语言·学习
paishishaba2 小时前
Maven
java·maven
张人玉3 小时前
C# 常量与变量
java·算法·c#
Java技术小馆3 小时前
GitDiagram如何让你的GitHub项目可视化
java·后端·面试