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