按月分隔时间段,java实现,DateRange

百度了一下,按月分隔时间的方法,普遍不是很易懂,或者代码太长,索性自己写了。同时,这个代码改成按周分隔,按小时分隔,按年分隔,都很容易,自己操作吧。

需要引入包:

<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帮我重构的。。。。人工智能万岁!

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值