百度了一下,按月分隔时间的方法,普遍不是很易懂,或者代码太长,索性自己写了。同时,这个代码改成按周分隔,按小时分隔,按年分隔,都很容易,自己操作吧。
需要引入包:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
类代码如下:
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.time.DateUtils;
public class DateRange {
private Date start;
private Date end;
public DateRange() {
super();
}
public DateRange(Date start, Date end) {
super();
this.start = start;
this.end = end;
}
/**
* 判断时间范围是否不为空,即开始时间不在结束时间之后
*/
public boolean isNotEmptyDateRange() {
return start.before(end) || start.equals(end);
}
public List<DateRange> splitByMonth() {
List<DateRange> result = new ArrayList<>();
if(!this.isNotEmptyDateRange()) {
return result;
}
Calendar sc = Calendar.getInstance();
sc.setTime(start);
Calendar ec = Calendar.getInstance();
ec.setTime(end);
if (this.isSameMonth(sc, ec)) {
result.add(this);
return result;
}
while(sc.before(ec)) {
DateRange d = new DateRange();
d.setStart(sc.getTime());
//向时间点之后取整,这里以月取整,就是下月初第一天零点
//还有一个方法是truncate是向之前的时间取整,和celling正好相反
//为什么celling有向后取整的意思,因为吊灯,天花板,之类的英文就是celling
sc = DateUtils.ceiling(sc, Calendar.MONDAY);
//这里主要考虑最后一个月的情况,这个if其实可以提到while外面,性能会更好一点
//我懒得提了
if(sc.before(ec)) {
d.setEnd(sc.getTime());
} else {
d.setEnd(ec.getTime());
}
result.add(d);
}
return result;
}
private boolean isSameMonth(final Calendar cal1, final Calendar cal2) {
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
&& cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH);
}
public Date getStart() {
return start;
}
public void setStart(Date start) {
this.start = start;
}
public Date getEnd() {
return end;
}
public void setEnd(Date end) {
this.end = end;
}
}
测试代码如下:
public static void main(String[] args) throws ParseException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
DateRange d = new DateRange(df.parse("2018-08-04 13:05:06"), df.parse("2019-06-05 12:25:36"));
List<DateRange> list = d.splitByMonth();
list.forEach(x -> System.out.println("start=" + df.format(x.getStart()) + ", end=" + df.format(x.getEnd())));
}
测试结果:
start=2018-08-04 13:05:06, end=2018-09-01 00:00:00
start=2018-09-01 00:00:00, end=2018-10-01 00:00:00
start=2018-10-01 00:00:00, end=2018-11-01 00:00:00
start=2018-11-01 00:00:00, end=2018-12-01 00:00:00
start=2018-12-01 00:00:00, end=2019-01-01 00:00:00
start=2019-01-01 00:00:00, end=2019-02-01 00:00:00
start=2019-02-01 00:00:00, end=2019-03-01 00:00:00
start=2019-03-01 00:00:00, end=2019-04-01 00:00:00
start=2019-04-01 00:00:00, end=2019-05-01 00:00:00
start=2019-05-01 00:00:00, end=2019-06-01 00:00:00
start=2019-06-01 00:00:00, end=2019-06-05 12:25:36
--------------------------分割线----------------------------
更新啦!!!
V2.0版本,提取了几个常用的方法,按月,按天,按小时,分隔时间。
import org.apache.commons.lang3.time.DateUtils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
public class DateRange {
private Date start;
private Date end;
/**
* @param start
* @param end
*/
public DateRange() {
super();
}
/**
* @param start
* @param end
*/
public DateRange(Date start, Date end) {
super();
this.start = start;
this.end = end;
}
public static DateRange getOverlap(Date s1, Date e1, Date s2, Date e2) {
DateRange result = new DateRange();
result.setStart(s1.after(s2) ? s1 : s2);
result.setEnd(e1.before(e2) ? e1 : e2);
return result;
}
public static DateRange getOverlap(Date s1, Date e1, DateRange range) {
return getOverlap(s1, e1, range.getStart(), range.getEnd());
}
public static DateRange getOverlap(DateRange r1, DateRange r2) {
return getOverlap(r1.getStart(), r1.getEnd(), r2.getStart(), r2.getEnd());
}
/**
* 判断时间范围是否不为空,即开始时间不在结束时间之后
*/
public boolean isNotEmptyDateRange() {
return start.before(end) || start.equals(end);
}
public List<DateRange> splitByMonth() {
return split(Calendar.MONTH);
}
public List<DateRange> splitByDayOfMonth() {
return split(Calendar.DAY_OF_MONTH);
}
public List<DateRange> splitByHour() {
return split(Calendar.HOUR_OF_DAY);
}
/**
*
* @param calendarField the field from {@code Calendar} or <code>SEMI_MONTH</code>
* @return
*/
public List<DateRange> split(int calendarField) {
List<DateRange> result = new ArrayList<>();
if(!this.isNotEmptyDateRange()) {
return result;
}
Calendar sc = Calendar.getInstance();
sc.setTime(start);
Calendar ec = Calendar.getInstance();
ec.setTime(end);
while(sc.before(ec)) {
DateRange d = new DateRange();
d.setStart(sc.getTime());
sc = DateUtils.ceiling(sc, calendarField);
if(sc.before(ec)) {
d.setEnd(sc.getTime());
} else {
d.setEnd(ec.getTime());
}
result.add(d);
}
return result;
}
private boolean isSameMonth(final Calendar cal1, final Calendar cal2) {
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
&& cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH);
}
public Date getStart() {
return start;
}
public void setStart(Date start) {
this.start = start;
}
public Date getEnd() {
return end;
}
public void setEnd(Date end) {
this.end = end;
}
public static SimpleDateFormat resultTimeDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
@Override
public String toString() {
return "DateRange [" + (start != null ? "start=" + resultTimeDF.format(start) + ", " : "") + (end != null ? "end=" + resultTimeDF.format(end) : "") + "]";
}
}
--------------------------分割线----------------------------
更新啦!!!
V3.0版本:
- 使用Lombok注解简化了getter和setter方法,以及toString方法。不用Lombok的同学,自己生成一下相关方法。
- 使用LocalDate和LocalDateTime代替Date,避免了时区和格式化的问题
- 使用Period和Duration类计算时间间隔,避免了Calendar的复杂操作
- 使用Stream API简化了split方法的逻辑
- 重命名了一些变量和方法,使其更符合Java规范
- 添加了一些注释和空行,提高了可读性
package com.dwt.ems.util;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data // 自动生成getter和setter方法,以及toString方法
@NoArgsConstructor // 自动生成无参构造方法
public class DateRange {
private LocalDateTime start; // 开始时间
private LocalDateTime end; // 结束时间
/**
* @param start 开始时间
* @param end 结束时间
*/
public DateRange(LocalDateTime start, LocalDateTime end) {
this.start = start;
this.end = end;
}
/**
* 获取两个时间范围的重叠部分
*
* @param s1 开始时间1
* @param e1 结束时间1
* @param s2 开始时间2
* @param e2 结束时间2
* @return 重叠的时间范围,如果没有重叠则返回null
*/
public static DateRange getOverlap(LocalDateTime s1, LocalDateTime e1, LocalDateTime s2, LocalDateTime e2) {
LocalDateTime overlapStart = s1.isAfter(s2) ? s1 : s2; // 取较晚的开始时间
LocalDateTime overlapEnd = e1.isBefore(e2) ? e1 : e2; // 取较早的结束时间
if (overlapStart.isBefore(overlapEnd)) { // 如果开始时间在结束时间之前,说明有重叠
return new DateRange(overlapStart, overlapEnd);
} else {
return null; // 否则没有重叠,返回null
}
}
public static DateRange getOverlap(LocalDateTime s1, LocalDateTime e1, DateRange range) {
return getOverlap(s1, e1, range.getStart(), range.getEnd());
}
public static DateRange getOverlap(DateRange r1, DateRange r2) {
return getOverlap(r1.getStart(), r1.getEnd(), r2.getStart(), r2.getEnd());
}
/**
* 判断时间范围是否不为空,即开始时间不在结束时间之后
*/
public boolean isNotEmpty() {
return start.isBefore(end) || start.equals(end);
}
public List<DateRange> splitByMonth() {
return split(ChronoUnit.MONTHS);
}
public List<DateRange> splitByDay() {
return split(ChronoUnit.DAYS);
}
public List<DateRange> splitByHour() {
return split(ChronoUnit.HOURS);
}
public List<DateRange> splitByMinute() {
return split(ChronoUnit.MINUTES);
}
public List<DateRange> splitBySecond() {
return split(ChronoUnit.SECONDS);
}
/**
* 按照指定的单位分割时间范围
*
* @param unit the unit to split the date range by, not null
* @return a list of sub date ranges, not null
*/
public List<DateRange> split(ChronoUnit unit) {
List<DateRange> result = new ArrayList<>();
if (!this.isNotEmpty()) { // 如果时间范围为空,直接返回空列表
return result;
}
// 使用Stream API生成从开始时间到结束时间的日期序列,按照指定的单位递增,并映射为DateRange对象,收集为列表返回
return Stream.iterate(start, date -> date.plus(1, unit)) // 从开始时间开始,每次加上一个单位的时间,生成一个无限流
.limit(unit.between(start, end) + 1) // 限制流的长度为两个时间之间的单位数加一
.map(date -> new DateRange(date, date.plus(1, unit))) // 将每个时间映射为一个DateRange对象,其开始时间为该时间,结束时间为该时间加上一个单位的时间
.peek(range -> { // 对每个DateRange对象进行处理,如果其结束时间超过了原始的结束时间,就将其设置为原始的结束时间
if (range.getEnd().isAfter(end)) {
range.setEnd(end);
}
})
.collect(Collectors.toList()); // 收集流中的元素为一个列表
}
}
哈哈哈,没错,V3.0是ChatGpt帮我重构的。。。。人工智能万岁!