java区间函数_基于Java8的可复用时间区间获取方法

在项目开发中,特别是报表展示的应用场景,我们经常会涉及到一些时间段的处理情况。例如本周,本月,上周,上月这种human reading的显示方式,其后台应转换为一个时间段,本文结合这个需求,提出一种可复用的方法,同时还包括在这个时间段内做一步sub-interval的方法。

基本数据结构

ReportDateRequest

首先从用户角度看,需要接收用户的选择,是一个约定俗成的时间区间,或是自定义的。该Request就封装了用户的时间区间选择,既包括一些固定时间段,也可以是自定义时间段,具体定义如下:

public class ReportDateRequest {

/**

* 时间请求的类型

* @see ReportDateCondition

*/

private int type;

/**

* 自定义情况下的上限(最晚的值)

*/

private long customUpper;

/**

* 自定义情况下的下限(最早的值)

*/

private long customLower;

type的定义使用一个枚举值固定下来,并根据每个值的不同特性,可转化为不同的时间段,如下。

ReportDateCondition

如上所述,RDC定义了可以选择的约定俗成的时间段的基本含义,具体定义如下(未列出构造函数):

public enum ReportDateCondition {

/**

* 任意时间段

*/

CUSTOM(0),

/**

* 当日

*/

TODAY(1),

/**

* 本周

*/

THIS_WEEK(2),

/**

* 上周

*/

LAST_WEEK(20),

/**

* 本月

*/

THIS_MONTH(3),

/**

* 上月

*/

LAST_MONTH(30)

}

枚举值中的code,即用于给前端标记当前用户选择的是何种时间区间。

ReportTimeLimit

有了枚举值,我们就可以将其转化为一种时间上下限,该类就是定义了一种时间上下限,根据每个枚举值可以生成一个ReportTimeLimit类。另外为了满足一些报表需要同比,环比的要求,在该类中再加入同比(上年同时间段)和环比(上日/周/月同时间段)的定义。具体描述如下

public class ReportTimeLimit {

/**

* 当前时间上限

*/

long currentUpper;

/**

* 当前时间下限

*/

long currentLower;

/**

* 环比上限

*/

long momUpper;

/**

* 环比下限

*/

long momLower;

/**

* 同比上限

*/

long yoyUpper;

/**

* 同比下限

*/

long yoyLower;

}

利用Java8时间函数确定时间段

有了以上的数据结构,我们就可以将一个约定俗成的时间区间,转换为long型的两个值,分别对应时间上限和下限。这是通过ReportDateCondition枚举中的方法实现的。

/**

* 时间类型的缺省方法不能获取时间段,如果调用的话返回异常

*

* @param reportDateRequest 仅限是custom时使用

* @return

*/

public ReportTimeLimit getReportTimeLimit(ReportDateRequest reportDateRequest) {

throw new AbstractMethodError();

}

注意此方法的默认实现是不允许调用的,因此每个枚举值要重载该方法获取包含不同上下限的ReportTimeLimit类,下面一个一个来看。

当日

@Override

public ReportTimeLimit getReportTimeLimit(ReportDateRequest reportDateRequest) {

ReportTimeLimit res = new ReportTimeLimit();

res.currentUpper = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));

res.currentLower = LocalDate.now().atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

res.yoyUpper = LocalDateTime.now().minusYears(1L).toEpochSecond(ZoneOffset.of("+8"));

res.yoyLower = LocalDate.now().minusYears(1L).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

res.momUpper = LocalDateTime.now().minusDays(1L).toEpochSecond(ZoneOffset.of("+8"));

res.momLower = LocalDate.now().minusDays(1L).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

return res;

}

此处利用Java8的新时间函数取时间,这里有一些非常好的,简单易懂的API可以构造需要的时间点,并返回long型的时间戳。

以下其他时间类型处理是类似的,只在API的选择上略有不同,不再赘述。

当周

@Override

public ReportTimeLimit getReportTimeLimit(ReportDateRequest reportDateRequest) {

ReportTimeLimit res = new ReportTimeLimit();

res.currentUpper = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));

// 需要自己实现第一周的算法

TemporalAdjuster FIRST_OF_WEEK = TemporalAdjusters.ofDateAdjuster(localDate -> localDate.minusDays(localDate.getDayOfWeek().getValue() - DayOfWeek.MONDAY.getValue()));

res.currentLower = LocalDate.now().with(FIRST_OF_WEEK).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

res.yoyUpper = LocalDateTime.now().minusYears(1L).toEpochSecond(ZoneOffset.of("+8"));

res.yoyLower = LocalDate.now().minusYears(1L).with(FIRST_OF_WEEK).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

res.momUpper = LocalDateTime.now().minusWeeks(1L).toEpochSecond(ZoneOffset.of("+8"));

res.momLower = LocalDate.now().minusWeeks(1L).with(FIRST_OF_WEEK).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

return res;

}

上周

@Override

public ReportTimeLimit getReportTimeLimit(ReportDateRequest reportDateRequest) {

ReportTimeLimit res = new ReportTimeLimit();

// 需要自己实现第一周的算法

TemporalAdjuster FIRST_OF_WEEK = TemporalAdjusters.ofDateAdjuster(localDate -> localDate.minusDays(localDate.getDayOfWeek().getValue() - DayOfWeek.MONDAY.getValue()));

res.currentUpper = LocalDate.now().with(FIRST_OF_WEEK).atStartOfDay().toEpochSecond(ZoneOffset.of("+8")) - 1L;

res.currentLower = LocalDate.now().minusWeeks(1L).with(FIRST_OF_WEEK).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

return res;

}

本月

@Override

public ReportTimeLimit getReportTimeLimit(ReportDateRequest reportDateRequest) {

ReportTimeLimit res = new ReportTimeLimit();

res.currentUpper = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));

res.currentLower = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

res.yoyUpper = LocalDateTime.now().minusYears(1L).toEpochSecond(ZoneOffset.of("+8"));

res.yoyLower = LocalDate.now().minusYears(1L).with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

res.momUpper = LocalDateTime.now().minusMonths(1L).toEpochSecond(ZoneOffset.of("+8"));

res.momLower = LocalDate.now().minusMonths(1L).with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

return res;

}

上月

@Override

public ReportTimeLimit getReportTimeLimit(ReportDateRequest reportDateRequest) {

ReportTimeLimit res = new ReportTimeLimit();

res.currentUpper = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay().toEpochSecond(ZoneOffset.of("+8")) - 1L;

res.currentLower = LocalDate.now().minusMonths(1L).with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay().toEpochSecond(ZoneOffset.of("+8"));

return res;

}

使用方法

在需要使用时间区间的方法中,用一个ReportDateRequest作为参数传入,通过转换和调用对应方法获取ReportTimeLimit即可从该对象中获取时间区间的上下限,用于各类数据库Criteria对象的值。

public ReturnResult getOrderStat( ReportDateRequest dateRequest,...) {

// ...

ReportDateCondition dateCondition = dateRequest.convertType();

ReportTimeLimit reportTimeLimit = dateCondition.getReportTimeLimit(dateRequest);

orderDetailCriteria.andOperator(

Criteria.where(KEY_OF_RELEASE_TIME).lte(reportTimeLimit.getCurrentUpper()),

Criteria.where(KEY_OF_RELEASE_TIME).gte(reportTimeLimit.getCurrentLower()));

// ...

}

获取下一级时间的间隔

需求

除了以上通过约定俗成的说法获取时间上下限之外,我们有时还需要在当前时间段内,获取一个下一级的时间间隔,例如

本日:获取每小时的时间间隔

本月/上月:获取每日的时间间隔

本周/上周:获取一周7天的时间间隔

自定义:获取自定义区间内每日的时间间隔

同时我们还希望在每个时间间隔上有个title,这样可以描述每个间隔的指标,例如一个该时间区间的柱状图或折线图。

核心方法

protected TreeMap getInterval(long upper, long lower, CoordinateBuilder coordinateBuilder, DateStepType dateStepType, Class indexClazz) {

TreeMap res = new TreeMap<>();

String[] coordinate = coordinateBuilder.getCoordinate();

Calendar calendar = Calendar.getInstance();

calendar.setTimeInMillis(lower);

int index = 0;

try {

while (calendar.getTimeInMillis() < upper) {

IndexWithCoordinate iwc = (IndexWithCoordinate) indexClazz.newInstance();

iwc.setCoordinate(coordinate[index]);

index++;

res.put(calendar.getTimeInMillis(), iwc);

calendar.add(dateStepType.getDateStepType(), 1);

}

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

return res;

}

IndexWithCoordinate:这是一个抽象类,仅包含一个描述该指标的坐标名字,最常见的是一个日期,例如"2019-02-02"

CoordinateBuilder: 这是一个函数式接口,用于返回一个数组,作为模板构造方法的返回值

DateStepType: 这是一个函数式接口,用于返回一个Calendar中的日期步进值

返回结果的map,是一个有序的map,key为每个间隔的时间下限,value为一个继承了IndexWithCoordinate的具体类

具体做法是这样的,首先用lower时间构造一个Calendar对象,然后逐步按DateStepType的返回值步进,每个时间构造一个IndexWithCoordinate对象,并通过CoordinateBuilder的模板找到具体的指标名字进行填充,最后放到返回的有序map中,直到Calendar对象的值超过upper为止

由该方法的几个参数配合,保证CoordinateBuilder的结果数组不会越界

使用方法

这个有序map可用于手工聚合时间类的数据结构,主要依据以下方法

private Long getSection(Long[] interval, long time) {

Long res = interval[0];

for (Long single : interval) {

if (time > single) {

res = single;

continue;

}

break;

}

return res;

}

interval数组即有序map的keyset转换的数组(一组时间戳),time是任意一个时间,通过遍历的方式可以获取这个time落到了哪个区间上,并返回该区间的下限。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值