java解析cron_【工具】cron 表达式解析 与 执行时间获取

package com.jplus.core.plugin.scheduling.cron;

import java.util.Arrays;

import java.util.Calendar;

import java.util.Date;

import java.util.List;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

import com.jplus.core.utill.Assert;

import com.jplus.core.utill.JUtil;

/**

* Cron 表达式解析与时间获取

*

*

 
   

* CronKit cron = new CronKit("0 0 1 1 * ? *");

* Date next = cron.next();

*

*

*

* @author Yuanqy https://my.oschina.net/jweb/blog/786444

*/

public class CronKit {

private final String cron;

private final Long[] crop;// 范围

private Lock lock = new ReentrantLock(); // 辅助Calendar线程安全

private final Calendar cal = Calendar.getInstance();// 实例化是线程安全的,但多个线程对同一Cal操作时是非线程安全的

/**

* 支持6-7位长度cron表达式

* 不支持字符:L W C #

* @param cron

*/

public CronKit(String cron) {

Assert.notNull(cron, "error: cron must be not null");

cron = cron.replaceAll("\\s+", " ").trim();

this.cron = cron;

this.crop = parse(cron);

}

public String getCron() {

return cron;

}

public Long getCrop(int i) {

return crop[i];

}

private Long getP(Bounds bound) {

return crop[bound.index];

}

// Java(Spring)

// * * * * * * *

// - - - - - - - 秒 分 时 天 月 周 年

// | | | | | | +--- year [optional(1970-2099)]

// | | | | | +----- day of week (1 - 7) (Sunday=7) L C #

// | | | | +------- month (1 - 12)

// | | | +--------- day of month (1 - 31) L W C

// | | +----------- hour (0 - 23)

// | +------------- min (0 - 59)

// +--------------- second (0 - 59)

private enum Bounds {

seconds(0, 0, 59, Calendar.SECOND),

minutes(1, 0, 59, Calendar.MINUTE),

hours(2, 0, 23, Calendar.HOUR_OF_DAY),

dom(3, 1, 31, Calendar.DAY_OF_MONTH), // Day1

months(4, 1, 12, Calendar.MONTH), // Calendar.MONTH的范围是0-11

dow(5, 1, 7, Calendar.DAY_OF_WEEK), // Day2

year(6, 1970, 9999, Calendar.YEAR);

public int index;

public int min;

public int max;

public int calType;

private Bounds(int index, int min, int max, int calType) {

this.index = index;

this.min = min;

this.max = max;

this.calType = calType;

}

public static Bounds getIndex(int index) {

for (Bounds en : Bounds.values())

if (en.index == index)

return en;

return null;

}

}

private List months = Arrays.asList(new String[]{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"});

private List dow = Arrays.asList(new String[]{"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"});

private Date lastTmp = new Date();// next缓存最后一次时间

// == parse 解析

private Long[] parse(String cron) {

String[] items = cron.split(" ");

Assert.isTrue(items.length == 6 || items.length == 7, "error: cron length must be 6 or 7 ");

Long[] cronPosition = new Long[7];

cronPosition[6] = ~0L; // 默认为年填充所有

for (int i = 0; i < items.length; i++)

cronPosition[i] = getField(items[i], i);

return cronPosition;

}

/**

* 获取区间范围

*/

private long getField(String field, int index) {

long bits = 0;

if (field.contains(",")) {// 判断一个节点多个条件,递归

String[] items = field.split(",");

for (String chars : items)

bits |= getField(chars, index);

} else {

int start = 0, end = 0, step = 1;

Bounds bound = Bounds.getIndex(index);

if (field.equals("*") || field.equals("?")) { // 是否仅有一个字符是 * 或者 ?。

start = bound.min;

end = bound.max;

} else if (field.indexOf("-") > 0) {// 是否可以"-"分解为俩数字

String[] items = field.split("-");

start = parseIntOrName(items[0], bound);

end = parseIntOrName(items[1], bound);

} else if (field.indexOf("/") > 0) {// 是否可以"/"分解为俩数字

String[] items = field.split("/");

end = bound.max;

start = bound.min;

if (!items[0].equals("*"))

start = parseIntOrName(items[0], bound);

step = parseIntOrName(items[1], bound);

} else {// 默认 单个

start = parseIntOrName(field, bound);

end = start;

}

Assert.isTrue(start >= bound.min, String.format("Start of range (%d) must above the minimum (%d):%s", start, bound.min, field));

Assert.isTrue(end <= bound.max, String.format("End of range (%d) must below the maximum (%d):%s", end, bound.max, field));

Assert.isTrue(start <= end, String.format("Start of range (%d) must below the end of range (%d):%s", start, end, field));

bits |= getBits(start % 100, end % 100, step);// %100主要为了解决year的问题

}

return bits;

}

/**

* [核心1]获取区间

* 原理: long类型二进制64位,cron

* 表达式所有的范围在0~59之间(除去第七位外),这样可以把每个可用的点映射到二进制相应的位上。1代表有权限

*/

private Long getBits(int start, int end, int step) {

Long bits = 0L;

if (step == 1) // 当step=1时,可以直接反位并异或获取范围值。 这个if其实可以不要,都用下方的for循环匹配也可以

return ~(Long.MAX_VALUE << (end + 1)) & (Long.MAX_VALUE << start);

for (int i = start; i <= end; i += step)

bits |= 1L << i;

return bits;

}

private int parseIntOrName(String field, Bounds bound) {

if (JUtil.isNumber(field))

return Integer.parseInt(field);

else {

field = field.toUpperCase();

if (bound == Bounds.months)

return months.indexOf(field) + 1;

else if (bound == Bounds.dow)

return dow.indexOf(field) + 1;

else

throw new RuntimeException("parseIntOrName No match found:" + field + "[" + bound.index + "]");

}

}

/**

* 获取下一次的执行时间

*/

public Date next() {

lastTmp = next(lastTmp);

return lastTmp;

}

public Date next(Date date) {

lock.lock();// 保证线程安全

try {

boolean refresh = true;

cal.setTime(date);

cal.set(Calendar.MILLISECOND, 0);// 毫秒清零

cal.add(Calendar.SECOND, 1);// +1秒

while (refresh) {

boolean again = false;

for (int i = 0; i < crop.length; i++) // 自小而大

again |= checkField(cal, Bounds.getIndex(i));

refresh = again;

}

return cal.getTime();

} catch (Exception e) {

throw new RuntimeException(e);

} finally {

lock.unlock();

}

}

/**

* [核心2]匹配时间 原理:通过位移后(位移方式和getBits方法一致) 判断 二进制范围内是否有匹配上的权限。

*/

private boolean checkField(Calendar cal, Bounds bound) {

while (((1L << (calGet(cal, bound) % 100)) & getP(bound)) == 0) {// &运算,当匹配上即不为0;

cal.add(bound.calType, 1);

if (calGet(cal, bound) == bound.min) {

clearChild(cal, bound);// 子集清零,当父级轮回,子集也得轮回

return true;// 已轮回,需要重新计算

}

}

return false;// 表示匹配完成

}

private int calGet(Calendar cal, Bounds bound) {

int val = cal.get(bound.calType);

if (bound == Bounds.months)

val += 1;// 如果是months得+1

return val;

}

private void clearChild(Calendar cal, Bounds bound) {

if (bound == Bounds.dow)// dow与dom 只要清空秒,分,时就行了,并不想清空月份

bound = Bounds.dom;

for (int i = bound.index - 1; i >= 0; i--) { // 自大而小

Bounds tmp = Bounds.getIndex(i);

cal.set(tmp.calType, tmp.min);

}

}

}

测试内容:

private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

@org.junit.Test

public void test_Cron() {

System.err.println(SDF.format(new Date()));

// CronKit cron = new CronKit("1,5 */23 3-5 2/3 * ? *");

// CronKit cron = new CronKit("0 0 1 1 * ? *");

CronKit cron = new CronKit("0/10 1 1 * * THU-SAT *");

System.out.println(JUtil.leftPad(Long.toBinaryString(cron.getCrop(0)), 64, "0") + ":秒 ");

System.out.println(JUtil.leftPad(Long.toBinaryString(cron.getCrop(1)), 64, "0") + ":分 ");

System.out.println(JUtil.leftPad(Long.toBinaryString(cron.getCrop(2)), 64, "0") + ":时 ");

System.out.println(JUtil.leftPad(Long.toBinaryString(cron.getCrop(3)), 64, "0") + ":天 ");

System.out.println(JUtil.leftPad(Long.toBinaryString(cron.getCrop(4)), 64, "0") + ":月 ");

System.out.println(JUtil.leftPad(Long.toBinaryString(cron.getCrop(5)), 64, "0") + ":周 ");

System.out.println(JUtil.leftPad(Long.toBinaryString(cron.getCrop(6)), 64, "0") + ":年 ");

for (int i = 0; i < 20; i++) {

System.out.println(SDF.format(cron.next()));

}

}

测试结果:

2019-04-03 14:56:38

0000000000000100000000010000000001000000000100000000010000000001:秒

0000000000000000000000000000000000000000000000000000000000000010:分

0000000000000000000000000000000000000000000000000000000000000010:时

0000000000000000000000000000000011111111111111111111111111111110:天

0000000000000000000000000000000000000000000000000001111111111110:月

0000000000000000000000000000000000000000000000000000000011100000:周

0000000000000000000000000000111111111111111110000000000000000000:年

2019-04-04 01:01:40

2019-04-04 01:01:50

2019-04-05 01:01:00

2019-04-05 01:01:10

2019-04-05 01:01:20

2019-04-05 01:01:30

2019-04-05 01:01:40

2019-04-05 01:01:50

2019-04-06 01:01:00

2019-04-06 01:01:10

2019-04-06 01:01:20

2019-04-06 01:01:30

2019-04-06 01:01:40

2019-04-06 01:01:50

2019-04-11 01:01:00

2019-04-11 01:01:10

2019-04-11 01:01:20

2019-04-11 01:01:30

2019-04-11 01:01:40

2019-04-11 01:01:50

你可能感兴趣的:(java解析cron)