计算预计工作完成时间

文章目录

计算预计完成时间工具类: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 是一个非常实用的工具类,它考虑到了工作日、节假日、补班日、双休日和非工作时间。无论是进行项目管理,还是简单地需要考虑具体的工作日和工作时间,这个工具类都将提供非常大的帮助。

相关推荐
m0_5719575829 分钟前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟4 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity5 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天5 小时前
java的threadlocal为何内存泄漏
java
caridle5 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^5 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋35 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx