业务场景:一个时间区间对多个时间区间取差集,也就是计算出一个时间区间内其他时间区间没有占有的时间段 如下图:
整个时间段为一天的开始至结尾,蓝色为占用的时间段(跨天情况带上日期计算 如2019-10-01 22:00:00-2019-10-02 07:00:00),橙色箭头为需要计算出的时间段
代码实现以一个月为维度,计算当前时间到月底的时间段内所有空闲时段
模拟数据库查询出的List<UnitTime>
@Data public class UnitTime { /** * 当天日期 YYYY-MM-dd */ private String dateTime; /** * 开始时间戳 */ private Integer startDate; /** * 结束时间戳 */ private Integer endDate; }DateUtil工具类
public class DateUtil { private static String LONGDATE_DATEMONTH = "yyyy-MM"; private static String LONGDATE_DATE = "yyyy-MM-dd"; /** * Map的Key,用于获取某月第一天{@link #LONGDATE_DATE}格式的日期 */ public static final String START_DATE = "START_DATE"; /** * Map的Key,用于获取某月最后一天{@link #LONGDATE_DATE}格式的日期 */ public static final String STOP_DATE = "STOP_DATE"; /** * 获得指定年月日期的月份第一天和最后一天 * 如果日期在本月以内 则返回当天日期和月末日期 * @param yearMonth 2019-05 * @return */ public static Map<String, String> getStartDateAndStopDate(String yearMonth) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat(LONGDATE_DATEMONTH); Calendar cale = getCalendar(sdf.parse(yearMonth)); SimpleDateFormat format = new SimpleDateFormat(LONGDATE_DATE); String firstDay, lastDay; if (isThisTime(cale.getTime().getTime(),LONGDATE_DATEMONTH)) { firstDay = format.format(new Date()); } else { cale.add(Calendar.MONTH, 0); cale.set(Calendar.DAY_OF_MONTH, 1); firstDay = format.format(cale.getTime()); } cale.add(Calendar.MONTH, 1); cale.set(Calendar.DAY_OF_MONTH, 0); lastDay = format.format(cale.getTime()); Map<String, String> date = new HashMap<String, String>(); date.put(START_DATE, firstDay); date.put(STOP_DATE, lastDay); return date; } public static Calendar getCalendar(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); return calendar; } /** * 时间是否在本月内 * @param time 时间戳 * @param pattern 匹配格式 * @return */ public static boolean isThisTime(long time, String pattern) { Date date = new Date(time); SimpleDateFormat sdf = new SimpleDateFormat(pattern); String param = sdf.format(date); String now = sdf.format(new Date()); if (param.equals(now)) { return true; } return false; } @Data @AllArgsConstructor @NoArgsConstructor public static class DateTimeStamp { private Long startTimeStamp; private Long endTimeStamp; } /** * 获取时间段每天开始和结束的时间戳 * @param beginTime * @param endTime * @return * @throws ParseException */ public static Map<String,DateTimeStamp> getAllTimeStamp(String beginTime, String endTime) throws ParseException{ Map<String,DateTimeStamp> allTimeStamp = Maps.newHashMap(); SimpleDateFormat sdf = new SimpleDateFormat(LONGDATE_DATE); Calendar cal = getCalendar(sdf.parse(beginTime)); // 判断是否是同一天 if(isDay(beginTime,endTime)) { setTimeStampList(allTimeStamp,cal); return allTimeStamp; } setTimeStampList(allTimeStamp,cal); while (true) { cal.add(Calendar.DAY_OF_MONTH, 1); if (sdf.parse(endTime).after(cal.getTime())) { setTimeStampList(allTimeStamp,cal); } else { break; } } setTimeStampList(allTimeStamp,cal); return allTimeStamp; } /** * 判断两个人日期是否为同一天 * @param beginDate * @param endDate * @return * @throws ParseException */ public static boolean isDay(String beginDate, String endDate) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat(LONGDATE_DATE); return org.apache.commons.lang.time.DateUtils.isSameDay(sdf.parse(beginDate),sdf.parse(endDate)); } private static void setTimeStampList(Map<String,DateTimeStamp> allTimeStamp, Calendar cal) { DateTimeStamp dateTimeStamp = new DateTimeStamp(); long beginSt = getFirstTime(cal).getTime() / 1000; dateTimeStamp.setStartTimeStamp(beginSt); long endSt = getLastTime(cal).getTime() / 1000; dateTimeStamp.setEndTimeStamp(endSt); String result = new SimpleDateFormat("yyyy-MM-dd").format(new Date(beginSt * 1000)); allTimeStamp.put(result,dateTimeStamp); } /** * 获取当天起始时间 * @param todayStart * @return */ public static Date getFirstTime(Calendar todayStart) { todayStart.set(Calendar.HOUR_OF_DAY, 0); todayStart.set(Calendar.MINUTE, 0); todayStart.set(Calendar.SECOND, 0); todayStart.set(Calendar.MILLISECOND, 0); return todayStart.getTime(); } /** * 获取当天结束时间 * @param todayEnd * @return */ public static Date getLastTime(Calendar todayEnd) { todayEnd.set(Calendar.HOUR_OF_DAY, 23); todayEnd.set(Calendar.MINUTE, 59); todayEnd.set(Calendar.SECOND, 59); todayEnd.set(Calendar.MILLISECOND, 999); return todayEnd.getTime(); } }逻辑处理Service
@Service public class TimeService { /** * 获取当月空闲时间 * 每个占用时间的结束等于下一次查找的开始 */ public Map<String, List<DateUtil.DateTimeStamp>> getIntervalTime(List<UnitTime> unitTimeList,String month) { Map<String,List<DateUtil.DateTimeStamp>> resultMap = Maps.newTreeMap(); try { // 获取当前时间到当前月最后一天的日期区间 {"start":"2019-10-25","end":"2019-10-31"} Map<String, String> dateInterval = DateUtil.getStartDateAndStopDate(month); // 获取日期区间内每一天的开始和结束时间戳 Map<String, DateUtil.DateTimeStamp> timeStampMap = DateUtil.getAllTimeStamp(dateInterval.get(DateUtil.START_DATE),dateInterval.get(DateUtil.STOP_DATE)); // 当前广告位所有占用时间 -> 根据日期分组 Map<String,List<UnitTime>> unitTimeMap = unitTimeList.stream().collect(Collectors.groupingBy(UnitTime::getDateTime)); for (Map.Entry<String, DateUtil.DateTimeStamp> entry : timeStampMap.entrySet()) { // currentUnitTimeList 当天的占用时间 List<UnitTime> currentUnitTimeList = unitTimeMap.get(entry.getKey()); List<DateUtil.DateTimeStamp> subList = Lists.newArrayList(); Long startTs = entry.getValue().getStartTimeStamp(); Long endTs = entry.getValue().getEndTimeStamp(); if (CollectionUtils.isEmpty(currentUnitTimeList)) { // 当天没有占用时间 则设置一整天的时间 DateUtil.DateTimeStamp resultTs = new DateUtil.DateTimeStamp(); resultTs.setStartTimeStamp(startTs); resultTs.setEndTimeStamp(endTs); subList.add(resultTs); resultMap.put(entry.getKey(),subList); continue; } // 根据开始时间排序 currentUnitTimeList.sort(Comparator.comparing(UnitTime::getStartDate)); for (UnitTime u : currentUnitTimeList) { if (u.getStartDate().longValue() == startTs && u.getEndDate().longValue() == endTs) { // 一整天全部占满 break; } if (u.getStartDate().longValue() > startTs) { // 当前占用的开始时间戳大于当天的开始时间戳 则当天开始时间到当前的开始时间为空闲 DateUtil.DateTimeStamp resultTs = new DateUtil.DateTimeStamp(); resultTs.setStartTimeStamp(startTs); resultTs.setEndTimeStamp(u.getStartDate().longValue()); subList.add(resultTs); if (u.getEndDate().longValue() < endTs) { // 当前结束时间戳小于当天结束时间戳 下一次循环的开始时间等于当前的结束时间 startTs = u.getEndDate().longValue(); if (currentUnitTimeList.get(currentUnitTimeList.size() - 1).getEndDate().longValue() == startTs) { // 当前为数组最后一个元素 则当前结束时间到当天结束时间空闲 DateUtil.DateTimeStamp resultLastTs = new DateUtil.DateTimeStamp(); resultLastTs.setStartTimeStamp(startTs); resultLastTs.setEndTimeStamp(endTs); subList.add(resultLastTs); } } else { break; } } else { startTs = u.getEndDate().longValue(); } } resultMap.put(entry.getKey(),subList); } }catch (ParseException e) { throw new RuntimeException("日期参数转换异常!"); } return resultMap; } }Test
@RunWith(SpringRunner.class) @SpringBootTest(classes={Application.class}) @WebAppConfiguration @Slf4j public class ApplicationTest { @Autowired TimeService timeService; @Test public void test() throws Exception{ List<UnitTime> unitTimeList = Lists.newArrayList(); UnitTime unitTime = new UnitTime(); // 2019-11-03 01:00:00 - 2019-11-03 04:30:00 unitTime.setDateTime("2019-11-03"); unitTime.setStartDate(1572714000); unitTime.setEndDate(1572726600); unitTimeList.add(unitTime); // 2019-11-03 07:00:00 - 2019-11-03 10:30:00 UnitTime unitTime2 = new UnitTime(); unitTime2.setDateTime("2019-11-03"); unitTime2.setStartDate(1572735600); unitTime2.setEndDate(1572748200); unitTimeList.add(unitTime2); // 2019-11-03 14:30:00 - 2019-11-03 21:30:00 UnitTime unitTime3 = new UnitTime(); unitTime3.setDateTime("2019-11-03"); unitTime3.setStartDate(1572762600); unitTime3.setEndDate(1572787800); unitTimeList.add(unitTime3); // 2019-11-04 00:00:00 - 2019-11-04 23:59:59 UnitTime unitTime4 = new UnitTime(); unitTime4.setDateTime("2019-11-26"); unitTime4.setStartDate(1572796800); unitTime4.setEndDate(1572883199); unitTimeList.add(unitTime4); Map<String, List<DateUtil.DateTimeStamp>> intervalMap = timeService.getIntervalTime(unitTimeList,"2019-11"); log.info("data:"+intervalMap.get("2019-11-03")); // data:[DateUtil.DateTimeStamp(startTimeStamp=1572710400, endTimeStamp=1572714000), // DateUtil.DateTimeStamp(startTimeStamp=1572726600, endTimeStamp=1572735600), // DateUtil.DateTimeStamp(startTimeStamp=1572748200, endTimeStamp=1572762600), // DateUtil.DateTimeStamp(startTimeStamp=1572787800, endTimeStamp=1572796799)] } }最后返回结果是当天到本月的空闲时段/如果不是本月则返回月份所有空闲时段
