Java 日期區間與日期區間集合的差集 代碼實現
項目中遇到一個需求,就是過濾出一個日期區間與一個日期區間集合的差集。
例如:
已有日期區間:[2019-07-01 00:00:00~2019-07-31 23:59:59]
日期區間集合爲:[{2019-07-01 00:00:00, 2019-07-10 23:59:59}, {2019-07-15 00:00:00, 2019-07-20 23:59:59}, {2019-07-22 00:00:00, 2019-07-25 23:59:59}]
目標結果:[{2019-07-11 00:00:00, 2019-07-14 23:59:59}, {2019-07-21 00:00:00, 2019-07-21 23:59:59}, {2019-07-26 00:00:00, 2019-07-31 23:59:59}]
0. 解決思路
- 測試數據準備
1.1 已知日期區間對象DateObject date
1.2 初始化日期區間集合List<DateObject> list
- 將日期集合list中的所有日期區間合併
- 已知日期區間
date
與list
求交集(找出list
中日期區間坐落到date
的交集intersection
)
- 已知日期區間
date
再與交集intersection
求差集differences
1. 定義包含[開始日期~結束日期]的對象DateObject
/**
* 包含開始日期和結束日期的對象
*/
public class DateObject {
private Date startDate;
private Date endDate;
public DateObject(Date startDate, Date endDate) {
this.startDate = startDate;
this.endDate = endDate;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
@Override
public String toString() {
return "DateObject{startDate=" + startDate + ", endDate=" + endDate + "}";
}
}
2. 初始化DateObject集合對象
/**
* 初始化DateObject集合對象
*
* @return 集合對象
* @throws Exception 異常
*/
private List<DateObject> init() throws Exception {
List<DateObject> list = new ArrayList<>();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
list.add(new DateObject(format.parse("2019-07-01 00:00:00"), format.parse("2019-07-10 23:59:59")));
list.add(new DateObject(format.parse("2019-07-15 00:00:00"), format.parse("2019-07-20 23:59:59")));
list.add(new DateObject(format.parse("2019-07-22 00:00:00"), format.parse("2019-07-25 23:59:59")));
list.sort(Comparator.comparing(DateObject::getStartDate));
return list;
}
3. DateObject集合中的日期區間合併
/**
* 時間區間合併
*
* @param list 日期集合
* @return 合併後日期集合
*/
private List<DateObject> merge(List<DateObject> list) {
if (list == null || list.size() == 0) {
return new ArrayList<>();
}
List<DateObject> result = new ArrayList<>();
DateObject first = list.get(0);
for (int i = 1; i < list.size(); i++) {
DateObject next = list.get(i);
// 合併區間,時間精確到秒(差1秒則爲連續)
if (next.getStartDate().getTime() < first.getEndDate().getTime() || next.getStartDate().getTime() - first.getEndDate().getTime() == 1000) {
first.setStartDate(new Date(Math.min(first.getStartDate().getTime(), next.getStartDate().getTime())));
first.setEndDate(new Date(Math.max(first.getEndDate().getTime(), next.getEndDate().getTime())));
} else {
// 沒有交集,直接添加
result.add(first);
first = next;
}
}
result.add(first);
return result;
}
4. 已知日期區間date
與日期區間集合list
求交集
分析:集合中時間區間遍歷與date相比較,兩個時間區間會有4中相交的情況,此處只討論有交集的情況。
/**
* 求開放時間區間與時間區間的所有交集
*
* @param dateObject 日期對象
* @param list 日期列表
* @return 日期交集
*/
private List<DateObject> intersection(DateObject dateObject, List<DateObject> list) {
List<DateObject> result = new ArrayList<>();
for (DateObject date : list) {
// 時間區間全部在開放時間區間內:ad.startDate <= open.startDate < open.endDate <= ad.endDate
// 則[open.startDate, open.endDate] 均不開放
if (date.getStartDate().getTime() <= dateObject.getStartDate().getTime()
&& date.getEndDate().getTime() >= dateObject.getEndDate().getTime()) {
// 開放時間區間已全部佔用
break;
}
// 時間區間全部在開放時間區間內:open.startDate <= ad.startDate < ad.endDate <= open.endDate
else if (date.getStartDate().getTime() >= dateObject.getStartDate().getTime()
&& date.getEndDate().getTime() <= dateObject.getEndDate().getTime()) {
result.add(new DateObject(date.getStartDate(), date.getEndDate()));
}
// 結束時間在開放時間區間內:ad.startDate <= open.startDate <= ad.endDate <= open.endDate
else if (date.getStartDate().getTime() <= dateObject.getStartDate().getTime()
&& date.getEndDate().getTime() >= dateObject.getStartDate().getTime()
&& date.getEndDate().getTime() <= dateObject.getEndDate().getTime()) {
result.add(new DateObject(dateObject.getStartDate(), date.getEndDate()));
}
// 開始時間在開放時間區間內:open.startDate <= ad.startDate <= open.endDate <= ad.endDate
else if (date.getEndDate().getTime() > dateObject.getEndDate().getTime()) {
result.add(new DateObject(date.getStartDate(), dateObject.getEndDate()));
}
}
return result;
}
5. 已知日期區間date
再與交集intersection
求差集differences
集合intersection中的日期區間已經排好序了,第一個元素的結束日期與下一個元素的開始日期就是要的結果,以此類推
/**
* 求差集
*
* @param dateObject 日期對象
* @param list 日期列表
* @return 差集
*/
private List<DateObject> differences(DateObject dateObject, List<DateObject> list) {
List<DateObject> result = new ArrayList<>();
DateObject first = list.get(0);
if (list.size() == 1) {
if (dateObject.getStartDate().getTime() < first.getStartDate().getTime()) {
result.add(new DateObject(dateObject.getStartDate(), DateUtils.getEndTimeOfDay(DateUtils.plusDays(first.getStartDate(), -1))));
}
if (dateObject.getEndDate().getTime() - first.getEndDate().getTime() > 1000) {
result.add(new DateObject(DateUtils.getFirstTimeOfDay(DateUtils.plusDays(first.getEndDate(), 1)), DateUtils.getEndTimeOfDay(dateObject.getEndDate())));
}
} else {
for (int i = 1; i < list.size(); i++) {
DateObject next = list.get(i);
result.add(new DateObject(DateUtils.getFirstTimeOfDay(DateUtils.plusDays(first.getEndDate(), 1)), DateUtils.getEndTimeOfDay(DateUtils.plusDays(next.getStartDate(), -1))));
first = next;
}
DateObject last = list.get(list.size() - 1);
if (dateObject.getEndDate().getTime() > last.getEndDate().getTime()) {
result.add(new DateObject(DateUtils.getFirstTimeOfDay(DateUtils.plusDays(last.getEndDate(), 1)), DateUtils.getEndTimeOfDay(dateObject.getEndDate())));
}
}
return result;
}
6. 測試類
@Test
public void test1() throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 時間區間
DateObject open = new DateObject(format.parse("2019-07-01 00:00:00"), format.parse("2019-07-31 23:59:59"));
// 時間區間列表合併
List<DateObject> merge = merge(init());
System.out.println("時間區間求並集:" + JSONObject.toJSONStringWithDateFormat(merge, "yyyy-MM-dd HH:mm:ss"));
// 求交集
List<DateObject> intersection = intersection(open, merge);
System.out.println("時間區間求交集:" + JSONObject.toJSONStringWithDateFormat(intersection, "yyyy-MM-dd HH:mm:ss"));
// 求差集
if (intersection.isEmpty()) {
System.out.println("該區間不可再選:" + JSONObject.toJSONStringWithDateFormat(open, "yyyy-MM-dd HH:mm:ss"));
return;
}
List<DateObject> list = differences(open, intersection);
System.out.println("時間區間求差集:" + JSONObject.toJSONStringWithDateFormat(list, "yyyy-MM-dd HH:mm:ss"));
}
測試結果:
時間區間求並集:[{"endDate":"2019-07-10 23:59:59","startDate":"2019-07-01 00:00:00"},{"endDate":"2019-07-20 23:59:59","startDate":"2019-07-15 00:00:00"},{"endDate":"2019-07-25 23:59:59","startDate":"2019-07-22 00:00:00"}]
時間區間求交集:[{"endDate":"2019-07-10 23:59:59","startDate":"2019-07-01 00:00:00"},{"endDate":"2019-07-20 23:59:59","startDate":"2019-07-15 00:00:00"},{"endDate":"2019-07-25 23:59:59","startDate":"2019-07-22 00:00:00"}]
時間區間求差集:[{"endDate":"2019-07-14 23:59:59","startDate":"2019-07-11 00:00:00"},{"endDate":"2019-07-21 23:59:59","startDate":"2019-07-21 00:00:00"},{"endDate":"2019-07-31 23:59:59","startDate":"2019-07-26 00:00:00"}]
7. DateUtils 類
public class DateUtils {
/**
* 獲取date當天的最後時間
*
* @param date 日期
* @return date當天的最後時間
*/
public static Date getEndTimeOfDay(Date date) {
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
return Date.from(LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 獲取date當天的開始日期
*
* @param date 日期
* @return date當天的開始日期
*/
public static Date getFirstTimeOfDay(Date date) {
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
return Date.from(LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant());
}
}