Oracle 解析cron表达式

  • 1.概述
  • 2.步骤
    • 2.1 规范cron表达式
      • 2.1.1 格式详解
      • 2.1.2 规范cron字符串
    • 2.2 根据空格切割cron字符串
    • 2.3 根据‘,’分割每一个时间位,获取预期运行list
    • 2.4 获取每一个时间位的开始值
    • 2.5 重新初始化每一个时间位的开始值
    • 2.6 获取每一个时间位的下次运行值
    • 2.7 判断日期合法性
    • 2.8 返回生成的日期字符串
  • 3.完整方法
    • 3.1 创建自定义数组cron_type_number
    • 3.2 创建函数cron_getnexttimeafter()
    • 3.3 调用示例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangguogangll/article/details/91359528

1.概述

最近工作中有一个需求,需要在数据库中解析cron定时表达式,获取定时任务的预期运行时间,然后和已运行的任务日志表关联查询,判断定时任务是否按时执行、应用程序是否出现过异常停止的情况。
找了很久没找到合适的,就根据java的CronExpression类中的getNextValidTimeAfter()方法,写了一个Oracle的自定义函数,用于实现根据传入的cron表达式、开始时间,获取下一次定时任务的执行时间。基于这个函数,可以拓展为获取指定时间段内的定时任务运行时间、以某一个时间开始运行多少次的每次运行时间。

2.步骤

解析过程主要分为以下几个步骤:

  1. 规范cron表达式 ,去除换行符、首尾两端空格、小写转大写、正则校验合规性,保证表达式格式正确;
  2. 根据 ‘空格’ 分割cron表达式,依次分割为秒、分、时、日、月、星、年(可为空)等6个或7个字段串,同时将月、星中的英文转换为数字;
  3. 根据 ‘,’ 分割每一个时间位,依次解析每一个分割后的串,获取每一个时间位的预期运行list;
  4. 将开始时间加一秒,然后获取到每一个时间位的开始值;
  5. 根据开始值、预期运行list,依次按照年、月、日、时、分、秒的顺序,初始化超范围的下级开始值,例如开始‘年’为2019年,预期运行年的值为【2020-2030】,则应将其他下级位的开始值置为对应位的最小值;
  6. 根据重新初始化后的开始值、预期运行list,依次按照秒、分、时、日、月、年的顺序,计算对应位的下次运行值,如果当前位的预期值小于开始值,则将它的上级位的开始值进位加1,例如开始‘秒’为23、开始‘分’为9,预期运行秒的值为【5、15、20】,则将‘分’的开始值加1,变为10;
  7. 根据计算出的每一个时间位的值,判断该时间日期是否合法,如果是2月30日、2月31日、4月31日、6月31日、9月31日、11月31日、非闰年的2月29日(由于上下两个整百非闰年分别为1900年、2100年,因此为了计算方便不考虑整百的非闰年情况),则将开始‘日’置为1、开始‘月’进位加1,回到 【步骤5】 继续计算;
  8. 返回生成的时间字符串,如果没有符合的则返回为空。

2.1 规范cron表达式

由于本人能力有限,暂时不考虑星期、L、W、C、#的计算,只实现了常规用法的功能,如果有人有好的思路或方法,期待您的共同探讨。

2.1.1 格式详解

字段名 字典范围 特殊字符 必填
Seconds 0-59 , - * /
Minutes 0-59 , - * /
Hours 0-23 , - * /
Day-of-month 1-31 , - * / ? L W C
Month 1-12 or JAN-DEC , - * /
Day-of-Week 1-7 or SUN-SAT , - * / ? L C #
Year 0001-2299 , - * /
* : 用来表示任意值;当"*"出现在"/"前时,表示该字段的开始值。
? : 只能用在“Day-of-month”和“Day-of-Week”这两个字段,表示没有特定的值,且两个字段有且必须有一个值为"?"。
- : 用来表示范围,例如在Hours字段配置“10-12”,解析过来就是小时数为10,11和12都满足。
, : 用来表示枚举值,例如在Day-of-Week字段配置“MON,WED,FRI”,解析过来就是星期一,星期三和星期五都满足。
/ : 用来表示增量逻辑,格式为“初始值/增量值”,例如在Seconds字段配置“5/15”,解析过来就是5,20,35,50都符合。
L : last的简写,只能用在“Day-of-month”和“Day-of-Week”这两个字段。
W : weekday的简写,只能用在“Day-of-month”字段,表示最靠近指定日期的工作日(星期一到星期五)。
C : 用来表示日历,但是浏览了网上已有的cron表达式解析,基本没有人用到此字符,所以本文也不考虑此字符。
# : 只能用在Day-of-Week字段,“m#n”表示这个月的第n个星期m,且n的范围为1-5、m的范围不限;当字段存在"#"时,其他分隔符不起作用,且会找最小值当m;当有"-"存在时,"#"不起作用。

2.1.2 规范cron字符串

--首尾两端去空格/去空白符换行符/去两个以上空格/转大写cron_var := upper(regexp_replace(replace(replace(replace(trim(cron),chr(9),''),chr(10),''),chr(13),''),'( ){2,}',' '));--判断格式是否合规,否则提示异常if regexp_count(cron_var,' ') is null or regexp_count(cron_var,' ') not in (5,6) thenraise_application_error(-20001,'定时字符串格式错误');end if;

2.2 根据空格切割cron字符串

在此过程中,同时将月、星期中的英文字符转换为数字方便后续计算。
切割完成后应该校验字符串的合规性,本方法暂时没处理,直接按照正确的格式看待。

--取值,根据空格分别获取
cron_seconds := regexp_substr(cron_var,'[^ ]+',1,1);--秒
cron_minutes := regexp_substr(cron_var,'[^ ]+',1,2);--分
cron_hours := regexp_substr(cron_var,'[^ ]+',1,3);--时
cron_day := regexp_substr(cron_var,'[^ ]+',1,4);--日
cron_month := cron_replace_month(regexp_substr(cron_var,'[^ ]+',1,5));--月
cron_week := cron_replace_week(regexp_substr(cron_var,'[^ ]+',1,6));--星
cron_year := nvl(regexp_substr(cron_var,'[^ ]+',1,7),'*');--年
--翻译替换,将月份中的英文字符转换为数字
function cron_replace_month(str varchar2)return varchar2asresult_str varchar2(128);beginresult_str := replace(str,'JAN','1');result_str := replace(result_str,'FEB','2');result_str := replace(result_str,'MAR','3');result_str := replace(result_str,'APR','4');result_str := replace(result_str,'MAY','5');result_str := replace(result_str,'JUN','6');result_str := replace(result_str,'JUL','7');result_str := replace(result_str,'AUG','8');result_str := replace(result_str,'SEP','9');result_str := replace(result_str,'OCT','10');result_str := replace(result_str,'NOV','11');result_str := replace(result_str,'DEC','12');return result_str;end;
--翻译替换,将星期中的英文字符转换为数字,特别说明英文中星期日为一周的开始
function cron_replace_week(str varchar2)return varchar2asresult_str varchar2(128);beginresult_str := replace(str,'SUN','1');result_str := replace(result_str,'MON','2');result_str := replace(result_str,'TUE','3');result_str := replace(result_str,'WED','4');result_str := replace(result_str,'THU','5');result_str := replace(result_str,'FRI','6');result_str := replace(result_str,'SAT','7');return result_str;end;

2.3 根据‘,’分割每一个时间位,获取预期运行list

  --获取预期的运行数组--为了提高效率,本函数将年的范围限定为1949年-2049年,如果有实际需要,可以酌情调整array_seconds := cron_get_str_array(cron_seconds,0,59);array_minutes := cron_get_str_array(cron_minutes,0,59);array_hours := cron_get_str_array(cron_hours,0,23);array_day := cron_get_str_array(cron_day,1,31);array_month := cron_get_str_array(cron_month,1,12);array_week := cron_get_str_array(cron_week,1,7);array_year := cron_get_str_array(cron_year,1949,2049);
--获取运行数组
function cron_get_str_array(str varchar2,min_num number,max_num number)return cron_type_number istemp_result_array cron_type_number;num number default 1;temp_array_1 cron_type_array;--临时数组1temp_array_2 l_cron_type_number;--临时数组2begintemp_result_array := cron_type_number();temp_array_1 := cron_get_str(str);for i in 1..temp_array_1.count looptemp_array_2 := cron_get_array(temp_array_1(i),min_num,max_num);temp_result_array.extend(temp_array_2.count);for j in 1..temp_array_2.count looptemp_result_array(num) := temp_array_2(j);num := num+1;end loop;end loop;return temp_result_array;end;
--根据','分割字符串,生成list
function cron_get_str(str varchar2)return cron_type_array istemp_result_array cron_type_array;beginfor i in 1..regexp_count(str,'[^,]+')+1 looptemp_result_array(i) := regexp_substr(str,'[^,]+',1,i);end loop;return temp_result_array;end;
--解析字符串,返回运行数组
function cron_get_array(str varchar2,min_num number,max_num number)return l_cron_type_number istemp_result_array_number l_cron_type_number; num number default 1;i number default 0;temp_str_1 varchar2(128);temp_str_2 varchar2(128);temp_str_3 varchar2(128);temp_str_4 varchar2(128);temp_min_num number default -1;--周期开始值temp_max_num number default -1;--周期结束值temp_mod_num number default 1;--周期频率/默认为1begin--start获取周期的开始值/结束值----值为"*"或"?"时,取该字段最大最小值之间的所有值if str = '*' or str = '?' thentemp_min_num := min_num;temp_max_num := max_num;----值为"数字"时,取该数字elsif regexp_like(str,'^[0-9]+$') thenif to_number(str) between min_num and max_num thentemp_min_num := to_number(str);temp_max_num := to_number(str);elseraise_application_error(-20001,'值的范围为'||min_num||'"-'||max_num||'"');end if;----值包含"/"时,根据"/"切割字符串,前半部分为范围,后半部分为频率elsif str like '%/%' thentemp_str_1 := regexp_substr(str,'[^/]+',1,1);temp_str_2 := regexp_substr(str,'[^/]+',1,2);if regexp_like(temp_str_2,'^[0-9]+$') and (to_number(temp_str_2) between 1 and max_num) thentemp_mod_num := to_number(temp_str_2);if temp_str_1 is null or temp_str_1='*' thentemp_min_num := min_num;temp_max_num := max_num;elsif regexp_like(temp_str_1,'^[0-9]+$') and (to_number(temp_str_1)<min_num or to_number(temp_str_1)>max_num) thenraise_application_error(-20001,'符号"/"前的值范围为"'||min_num||'-'||max_num||'"');elsif regexp_like(temp_str_1,'^[0-9]+$') and (to_number(temp_str_1) between min_num and max_num) thentemp_min_num := to_number(temp_str_1);temp_max_num := max_num;elsif temp_str_1 like '%-%' thentemp_str_3 := regexp_substr(temp_str_1,'[^-]+',1,1);temp_str_4 := regexp_substr(temp_str_1,'[^-]+',1,2);if regexp_like(temp_str_3,'^[0-9]+$') and regexp_like(temp_str_4,'^[0-9]+$') and (to_number(temp_str_3) between min_num and max_num) and (to_number(temp_str_4) between min_num and max_num) thenif to_number(temp_str_3)<=to_number(temp_str_4) thentemp_min_num := to_number(temp_str_3);temp_max_num := to_number(temp_str_4);elsetemp_min_num := to_number(temp_str_3);temp_max_num := to_number(temp_str_4)+max_num-min_num+1;--此处是为了处理秒分时的开始值是0,日月年星的开始值是1,当开始值是0时加1,当开始值是1时不变end if;else raise_application_error(-20001,'符号"/"前的值格式错误');end if;else raise_application_error(-20001,'符号"/"前的值格式错误');end if;else raise_application_error(-20001,'符号"/"后必须有数字格式的值,且必须>1'||'、<='||max_num);end if;----值包含"-"时,取两个值之间的所有值,频率为1elsif str like '%-%' thentemp_str_1 := regexp_substr(str,'[^-]+',1,1);temp_str_2 := regexp_substr(str,'[^-]+',1,2);if regexp_like(temp_str_1,'^[0-9]+$') and regexp_like(temp_str_2,'^[0-9]+$') and (to_number(temp_str_1) between min_num and max_num) and (to_number(temp_str_2) between min_num and max_num) thenif to_number(temp_str_1)<=to_number(temp_str_2) thentemp_min_num := to_number(temp_str_1);temp_max_num := to_number(temp_str_2);elsetemp_min_num := to_number(temp_str_1);temp_max_num := to_number(temp_str_2)+max_num-min_num+1;--此处是为了处理秒分时的开始值是0,日月年星的开始值是1,当开始值是0时加1,当开始值是1时不变end if;else raise_application_error(-20001,'符号"-"前的值格式错误');end if;elsereturn temp_result_array_number;end if;i := temp_min_num;while i<=temp_max_num loopif i<=max_num thentemp_result_array_number(num) := i;elsetemp_result_array_number(num) := i-max_num+min_num-1;--此处是为了处理秒分时的开始值是0,日月年星的开始值是1end if;i := i+temp_mod_num;num := num+1;end loop;--end获取周期数组return temp_result_array_number;end;

2.4 获取每一个时间位的开始值

此处注意,因为计算的是从指定时间开始的下次运行时间,因此将开始‘秒’加1。
这里用到了校验时间字符串是否合法的函数,如果能保证开始时间正确,则校验这一步可省略,适当提高效率。
开始时间可传可不传,如果不传,则默认为当前时间。

--校验开始时间格式if cron_is_date(start_time)=1 thenstart_seconds := substr(start_time,18,2)+1;start_minutes := substr(start_time,15,2);start_hours := substr(start_time,12,2);start_day := substr(start_time,9,2);start_month := substr(start_time,6,2);--start_week_1 := to_char(to_date(start_time,'yyyy-MM-dd hh24:mi:ss'),'D');--start_week_2 := to_char(to_date(start_time,'yyyy-MM-dd hh24:mi:ss'),'W');start_year := substr(start_time,1,4);next_year := substr(start_time,1,4);else raise_application_error(-20001,'开始时间格式错误,正确格式为"yyyy-MM-dd hh24:mi:ss"');end if;
--校验时间格式是否正确
function cron_is_date(str VARCHAR2) return number ISval date;beginval := TO_DATE(NVL(str, 'w'), 'yyyy-mm-dd hh24:mi:ss');return 1;exceptionwhen others then return 0;end;

2.5 重新初始化每一个时间位的开始值

按照年、月、日、时、分、秒的顺序依次处理。
因为考虑时间进位的情况,所以如果某一个时间位进位,则对应的下级时间开始值应该重新初始化。

--初始化超范围的下级<<goto_day_month_year>>select count(0) into temp_num from table(array_year) where column_value=start_year;if temp_num = 0 thenselect min(column_value) into start_month from table(array_month);select min(column_value) into start_day from table(array_day);select min(column_value) into start_hours from table(array_hours);select min(column_value) into start_minutes from table(array_minutes);select min(column_value) into start_seconds from table(array_seconds);end if;select count(0) into temp_num from table(array_month) where column_value=start_month;if temp_num = 0 thenselect min(column_value) into start_day from table(array_day);select min(column_value) into start_hours from table(array_hours);select min(column_value) into start_minutes from table(array_minutes);select min(column_value) into start_seconds from table(array_seconds);end if;select count(0) into temp_num from table(array_day) where column_value=start_day;if temp_num = 0 thenselect min(column_value) into start_hours from table(array_hours);select min(column_value) into start_minutes from table(array_minutes);select min(column_value) into start_seconds from table(array_seconds);end if;select count(0) into temp_num from table(array_hours) where column_value=start_hours;if temp_num = 0 thenselect min(column_value) into start_minutes from table(array_minutes);select min(column_value) into start_seconds from table(array_seconds);end if;select count(0) into temp_num from table(array_minutes) where column_value=start_minutes;if temp_num = 0 thenselect min(column_value) into start_seconds from table(array_seconds);end if;

2.6 获取每一个时间位的下次运行值

按照秒、分、时、日、月、年的顺序依次计算。
如果当前时间位的开始值超过预期运行值,则将下次运行值置为预期运行的最小值,同时上级时间进位加1。

--获取预期的运行时间next_seconds := cron_get_next_value(array_seconds,start_seconds);if start_seconds> next_seconds thenstart_minutes := start_minutes+1;end if;next_minutes := cron_get_next_value(array_minutes,start_minutes);if start_minutes> next_minutes thenstart_hours := start_hours+1;end if;next_hours := cron_get_next_value(array_hours,start_hours);if start_hours> next_hours thenstart_day := start_day+1;end if;next_day := cron_get_next_value(array_day,start_day);if start_day> next_day thenstart_month := start_month+1;end if;next_month := cron_get_next_value(array_month,start_month);--next_week_1 := cron_get_next_value(array_week,start_week_1);if start_month> next_month thenstart_year := start_year+1;end if;select min(to_number(column_value)) into next_year from table(array_year) where to_number(column_value)>=start_year;if next_year is null then--raise_application_error(-20001,'在指定年"'||array_year(array_year.first)||'-'||array_year(array_year.last)||'"的范围内没有预期运行的时间');return '';end if;

2.7 判断日期合法性

如果生成的日期不合法,则返回步骤2.5重新计算,直到超出年度范围。

--判断日期是否合法,不合法则以此为新的开始时间重新计算if next_year<=array_year(array_year.last) and ((mod(next_year,4)!=0 and next_month=2 and next_day=29) or (next_month=2 and next_day=30) or (next_month in(2,4,6,9,11) and next_day=31)) thenstart_year := next_year;start_month := next_month+1;start_day := 1;goto goto_day_month_year;end if;

2.8 返回生成的日期字符串

返回字符串的格式为‘yyyy-MM-dd hh24:mi:ss’。
判断生成的时间是否合法、是否小于开始时间,如果小于开始时间,则返回为空。
校验生成的时间是否合法这一判断可以省略,为了提高效率的话,可以去掉。

  result_time := lpad(next_year,4,'0')||'-'||lpad(next_month,2,'0')||'-'||lpad(next_day,2,'0')||' '||lpad(next_hours,2,'0')||':'||lpad(next_minutes,2,'0')||':'||lpad(next_seconds,2,'0');--dbms_output.put_line(result_time);--判断生成的时间是否合法、是否小于开始时间,如果小于开始时间,则返回为空if cron_is_date(result_time)=0 or result_time<=start_time thenresult_time := '';end if;

3.完整方法

3.1 创建自定义数组cron_type_number

本方法中用到了自定义数组,需要单独创建

create or replace type cron_type_number as table of number(4);

3.2 创建函数cron_getnexttimeafter()

create or replace function cron_getnexttimeafter(cron varchar2,start_time varchar2 default to_char(sysdate,'yyyy-MM-dd hh24:mi:ss'))
return varchar2
as
result_time varchar2(1024);--返回结果
type l_cron_type_number is table of number(4) index by binary_integer;
type cron_type_array is table of varchar2(128)  index by binary_integer;
cron_var varchar2(1024);
temp_num number;
cron_seconds varchar2(512);--秒
cron_minutes varchar2(512);--分
cron_hours varchar2(512);--时
cron_day varchar2(512);--日
cron_month varchar2(512);--月
cron_week varchar2(512);--星
cron_year varchar2(512);--年array_seconds cron_type_number;--秒的数组
array_minutes cron_type_number;--分的数组
array_hours cron_type_number;--时的数组
array_day cron_type_number;--日的数组
array_month cron_type_number;--月的数组
array_week cron_type_number;--星的数组
array_year cron_type_number;--年的数组start_seconds number default -1;--开始时间-秒
start_minutes number default -1;--开始时间-分
start_hours number default -1;--开始时间-时
start_day number default -1;--开始时间-日
start_month number default -1;--开始时间-月
start_week_1 number default -1;--开始时间-星-星期几
start_week_2 number default -1;--开始时间-星-第几个
start_year number default -1;--开始时间-年next_seconds number default -1;--预期时间-秒
next_minutes number default -1;--预期时间-分
next_hours number default -1;--预期时间-时
next_day number default -1;--预期时间-日
next_month number default -1;--预期时间-月
next_week_1 number default -1;--预期时间-星-星期几
next_week_2 number default -1;--预期时间-星-第几个
next_year number default -1;--预期时间-年------------------------------------------------------------------------------------------------------
--根据','分割字符串,生成list
function cron_get_str(str varchar2)return cron_type_array istemp_result_array cron_type_array;beginfor i in 1..regexp_count(str,'[^,]+')+1 looptemp_result_array(i) := regexp_substr(str,'[^,]+',1,i);end loop;return temp_result_array;end;
------------------------------------------------------------------------------------------------------
--解析字符串,返回运行数组
function cron_get_array(str varchar2,min_num number,max_num number)return l_cron_type_number istemp_result_array_number l_cron_type_number; num number default 1;i number default 0;temp_str_1 varchar2(128);temp_str_2 varchar2(128);temp_str_3 varchar2(128);temp_str_4 varchar2(128);temp_min_num number default -1;--周期开始值temp_max_num number default -1;--周期结束值temp_mod_num number default 1;--周期频率/默认为1begin--start获取周期的开始值/结束值----值为"*"或"?"时,取该字段最大最小值之间的所有值if str = '*' or str = '?' thentemp_min_num := min_num;temp_max_num := max_num;----值为"数字"时,取该数字elsif regexp_like(str,'^[0-9]+$') thenif to_number(str) between min_num and max_num thentemp_min_num := to_number(str);temp_max_num := to_number(str);elseraise_application_error(-20001,'值的范围为'||min_num||'"-'||max_num||'"');end if;----值包含"/"时,根据"/"切割字符串,前半部分为范围,后半部分为频率elsif str like '%/%' thentemp_str_1 := regexp_substr(str,'[^/]+',1,1);temp_str_2 := regexp_substr(str,'[^/]+',1,2);if regexp_like(temp_str_2,'^[0-9]+$') and (to_number(temp_str_2) between 1 and max_num) thentemp_mod_num := to_number(temp_str_2);if temp_str_1 is null or temp_str_1='*' thentemp_min_num := min_num;temp_max_num := max_num;elsif regexp_like(temp_str_1,'^[0-9]+$') and (to_number(temp_str_1)<min_num or to_number(temp_str_1)>max_num) thenraise_application_error(-20001,'符号"/"前的值范围为"'||min_num||'-'||max_num||'"');elsif regexp_like(temp_str_1,'^[0-9]+$') and (to_number(temp_str_1) between min_num and max_num) thentemp_min_num := to_number(temp_str_1);temp_max_num := max_num;elsif temp_str_1 like '%-%' thentemp_str_3 := regexp_substr(temp_str_1,'[^-]+',1,1);temp_str_4 := regexp_substr(temp_str_1,'[^-]+',1,2);if regexp_like(temp_str_3,'^[0-9]+$') and regexp_like(temp_str_4,'^[0-9]+$') and (to_number(temp_str_3) between min_num and max_num) and (to_number(temp_str_4) between min_num and max_num) thenif to_number(temp_str_3)<=to_number(temp_str_4) thentemp_min_num := to_number(temp_str_3);temp_max_num := to_number(temp_str_4);elsetemp_min_num := to_number(temp_str_3);temp_max_num := to_number(temp_str_4)+max_num-min_num+1;--此处是为了处理秒分时的开始值是0,日月年星的开始值是1,当开始值是0时加1,当开始值是1时不变end if;else raise_application_error(-20001,'符号"/"前的值格式错误');end if;else raise_application_error(-20001,'符号"/"前的值格式错误');end if;else raise_application_error(-20001,'符号"/"后必须有数字格式的值,且必须>1'||'、<='||max_num);end if;----值包含"-"时,取两个值之间的所有值,频率为1elsif str like '%-%' thentemp_str_1 := regexp_substr(str,'[^-]+',1,1);temp_str_2 := regexp_substr(str,'[^-]+',1,2);if regexp_like(temp_str_1,'^[0-9]+$') and regexp_like(temp_str_2,'^[0-9]+$') and (to_number(temp_str_1) between min_num and max_num) and (to_number(temp_str_2) between min_num and max_num) thenif to_number(temp_str_1)<=to_number(temp_str_2) thentemp_min_num := to_number(temp_str_1);temp_max_num := to_number(temp_str_2);elsetemp_min_num := to_number(temp_str_1);temp_max_num := to_number(temp_str_2)+max_num-min_num+1;--此处是为了处理秒分时的开始值是0,日月年星的开始值是1,当开始值是0时加1,当开始值是1时不变end if;else raise_application_error(-20001,'符号"-"前的值格式错误');end if;elsereturn temp_result_array_number;end if;--end获取周期的开始值/结束值--start获取周期数组i := temp_min_num;while i<=temp_max_num loopif i<=max_num thentemp_result_array_number(num) := i;elsetemp_result_array_number(num) := i-max_num+min_num-1;--此处是为了处理秒分时的开始值是0,日月年星的开始值是1end if;i := i+temp_mod_num;num := num+1;end loop;--end获取周期数组return temp_result_array_number;end;
------------------------------------------------------------------------------------------------------
--获取运行数组
function cron_get_str_array(str varchar2,min_num number,max_num number)return cron_type_number istemp_result_array cron_type_number;num number default 1;temp_array_1 cron_type_array;--临时数组1temp_array_2 l_cron_type_number;--临时数组2begintemp_result_array := cron_type_number();temp_array_1 := cron_get_str(str);for i in 1..temp_array_1.count looptemp_array_2 := cron_get_array(temp_array_1(i),min_num,max_num);temp_result_array.extend(temp_array_2.count);for j in 1..temp_array_2.count looptemp_result_array(num) := temp_array_2(j);num := num+1;end loop;end loop;return temp_result_array;end;
------------------------------------------------------------------------------------------------------
--根据运行数组/开始时间的实际值,获取下次运行的值
function cron_get_next_value(cron_array cron_type_number,start_num number)return numberasresult_num number;beginselect min(to_number(column_value)) into result_num from table(cron_array) where to_number(column_value)>=start_num;if result_num is null thenselect min(to_number(column_value)) into result_num  from table(cron_array);end if;return result_num;end;
------------------------------------------------------------------------------------------------------
--翻译替换,将月份中的英文字符转换为数字
function cron_replace_month(str varchar2)return varchar2asresult_str varchar2(128);beginresult_str := replace(str,'JAN','1');result_str := replace(result_str,'FEB','2');result_str := replace(result_str,'MAR','3');result_str := replace(result_str,'APR','4');result_str := replace(result_str,'MAY','5');result_str := replace(result_str,'JUN','6');result_str := replace(result_str,'JUL','7');result_str := replace(result_str,'AUG','8');result_str := replace(result_str,'SEP','9');result_str := replace(result_str,'OCT','10');result_str := replace(result_str,'NOV','11');result_str := replace(result_str,'DEC','12');return result_str;end;
------------------------------------------------------------------------------------------------------
--翻译替换,将星期中的英文字符转换为数字,特别说明英文中星期日为一周的开始
function cron_replace_week(str varchar2)return varchar2asresult_str varchar2(128);beginresult_str := replace(str,'SUN','1');result_str := replace(result_str,'MON','2');result_str := replace(result_str,'TUE','3');result_str := replace(result_str,'WED','4');result_str := replace(result_str,'THU','5');result_str := replace(result_str,'FRI','6');result_str := replace(result_str,'SAT','7');return result_str;end;
------------------------------------------------------------------------------------------------------
--校验时间格式是否正确
function cron_is_date(str VARCHAR2) return number ISval date;beginval := TO_DATE(NVL(str, 'w'), 'yyyy-mm-dd hh24:mi:ss');return 1;exceptionwhen others then return 0;end;
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
begin--校验开始时间格式if cron_is_date(start_time)=1 thenstart_seconds := substr(start_time,18,2)+1;start_minutes := substr(start_time,15,2);start_hours := substr(start_time,12,2);start_day := substr(start_time,9,2);start_month := substr(start_time,6,2);--start_week_1 := to_char(to_date(start_time,'yyyy-MM-dd hh24:mi:ss'),'D');--start_week_2 := to_char(to_date(start_time,'yyyy-MM-dd hh24:mi:ss'),'W');start_year := substr(start_time,1,4);next_year := substr(start_time,1,4);else raise_application_error(-20001,'开始时间格式错误,正确格式为"yyyy-MM-dd hh24:mi:ss"');end if;--首尾两端去空格/去空白符换行符/去两个以上空格/转大写cron_var := upper(regexp_replace(replace(replace(replace(trim(cron),chr(9),''),chr(10),''),chr(13),''),'( ){2,}',' '));--判断格式是否合规,否则提示异常if regexp_count(cron_var,' ') is null or regexp_count(cron_var,' ') not in (5,6) thenraise_application_error(-20001,'定时字符串格式错误');end if;--取值,根据空格分别获取cron_seconds := regexp_substr(cron_var,'[^ ]+',1,1);--秒cron_minutes := regexp_substr(cron_var,'[^ ]+',1,2);--分cron_hours := regexp_substr(cron_var,'[^ ]+',1,3);--时cron_day := regexp_substr(cron_var,'[^ ]+',1,4);--日cron_month := cron_replace_month(regexp_substr(cron_var,'[^ ]+',1,5));--月cron_week := cron_replace_week(regexp_substr(cron_var,'[^ ]+',1,6));--星cron_year := nvl(regexp_substr(cron_var,'[^ ]+',1,7),'*');--年--正则校验格式是否正确--获取预期的运行数组--为了提高效率,本函数将年的范围限定为1949年-2049年,如果有实际需要,可以酌情调整array_seconds := cron_get_str_array(cron_seconds,0,59);array_minutes := cron_get_str_array(cron_minutes,0,59);array_hours := cron_get_str_array(cron_hours,0,23);array_day := cron_get_str_array(cron_day,1,31);array_month := cron_get_str_array(cron_month,1,12);array_week := cron_get_str_array(cron_week,1,7);array_year := cron_get_str_array(cron_year,1949,2049);-----------------------------------------------------------------------------------------------------------------------初始化超范围的下级<<goto_day_month_year>>select count(0) into temp_num from table(array_year) where column_value=start_year;if temp_num = 0 thenselect min(column_value) into start_month from table(array_month);select min(column_value) into start_day from table(array_day);select min(column_value) into start_hours from table(array_hours);select min(column_value) into start_minutes from table(array_minutes);select min(column_value) into start_seconds from table(array_seconds);end if;select count(0) into temp_num from table(array_month) where column_value=start_month;if temp_num = 0 thenselect min(column_value) into start_day from table(array_day);select min(column_value) into start_hours from table(array_hours);select min(column_value) into start_minutes from table(array_minutes);select min(column_value) into start_seconds from table(array_seconds);end if;select count(0) into temp_num from table(array_day) where column_value=start_day;if temp_num = 0 thenselect min(column_value) into start_hours from table(array_hours);select min(column_value) into start_minutes from table(array_minutes);select min(column_value) into start_seconds from table(array_seconds);end if;select count(0) into temp_num from table(array_hours) where column_value=start_hours;if temp_num = 0 thenselect min(column_value) into start_minutes from table(array_minutes);select min(column_value) into start_seconds from table(array_seconds);end if;select count(0) into temp_num from table(array_minutes) where column_value=start_minutes;if temp_num = 0 thenselect min(column_value) into start_seconds from table(array_seconds);end if;-----------------------------------------------------------------------------------------------------------------------获取预期的运行时间next_seconds := cron_get_next_value(array_seconds,start_seconds);if start_seconds> next_seconds thenstart_minutes := start_minutes+1;end if;next_minutes := cron_get_next_value(array_minutes,start_minutes);if start_minutes> next_minutes thenstart_hours := start_hours+1;end if;next_hours := cron_get_next_value(array_hours,start_hours);if start_hours> next_hours thenstart_day := start_day+1;end if;next_day := cron_get_next_value(array_day,start_day);if start_day> next_day thenstart_month := start_month+1;end if;next_month := cron_get_next_value(array_month,start_month);--next_week_1 := cron_get_next_value(array_week,start_week_1);if start_month> next_month thenstart_year := start_year+1;end if;select min(to_number(column_value)) into next_year from table(array_year) where to_number(column_value)>=start_year;if next_year is null thenreturn '';end if;--判断日期是否合法,不合法则以此为新的开始时间重新计算if next_year<=array_year(array_year.last) and ((mod(next_year,4)!=0 and next_month=2 and next_day=29) or (next_month=2 and next_day=30) or (next_month in(2,4,6,9,11) and next_day=31)) thenstart_year := next_year;start_month := next_month+1;start_day := 1;goto goto_day_month_year;end if;result_time := lpad(next_year,4,'0')||'-'||lpad(next_month,2,'0')||'-'||lpad(next_day,2,'0')||' '||lpad(next_hours,2,'0')||':'||lpad(next_minutes,2,'0')||':'||lpad(next_seconds,2,'0');--dbms_output.put_line(result_time);--判断生成的时间是否合法、是否小于开始时间,如果小于开始时间,则返回为空if cron_is_date(result_time)=0 or result_time<=start_time thenresult_time := '';end if;
return result_time;
end;

3.3 调用示例

--因为不存在2月31日,所以执行结果为空
select cron_getnexttimeafter('0 0/1 1 31 2 ? 2019-2029') from dual;
--执行结果为‘2020-02-02 01:00:00’
select cron_getnexttimeafter('0 0/1 1 2 2 ? 2019-2029','2019-07-07 17:22:00') from dual;
--执行结果为当前时间的下一个3月7日0点0分0秒
select cron_getnexttimeafter('0 0 0 7 3 ?') from dual;

参考文档:
1:https://blog.csdn.net/tengdazhang770960436/article/details/82253482

Oracle 解析cron定时表达式相关推荐

  1. C#/.NET 解析Cron表达式,根据Cron表达式获取最近执行时间

    Cron表达式定义及详情 请参考https://blog.csdn.net/HybridTheory_/article/details/88382442 使用C#解析Cron表达式,得到执行时间 cl ...

  2. java 解析cron_Java 解析Cron表达式,获取最近运行时间

    摘要:Java 解析Cron表达式,获取最近运行时间 方法一 package com.odj.customer.index.controller; import java.text.ParseExce ...

  3. Java自定义Cron,解析Cron表达式

    Cron表达式中周和数字是不对应的 周一:2:周二:3:周三:4:周四:5:周五:6:周六:7:周日:1 pom文件必须依赖 <dependency><groupId>com. ...

  4. oracle job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务。...

    oracle job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务. 一.查询系统中的job,可以查询视图 --相关视图 select * from dba_jobs; selec ...

  5. quartz/Cron/Crontab表达式在线生成工具

    cron表达式在指定定时任务时具有非常强的灵活性,可以满足日常遇到的各种定时规则.但是其规则设置起来还是有一定的难度,特别是不经常使用的时候,更容易忘记写法.通过图形化的方式进行配置,并且可以生成规则 ...

  6. ElasticJob corn定时表达式语法(亲测)

    秒(0~59) 分钟(0~59) 小时(0~23) 天(月)(0~31,但是你需要考虑你月的天数) 月(0~11) 天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI, ...

  7. Oracle Sql语句定时执行

    Oracle Sql语句定时执行 本文链接:https://blog.csdn.net/qq_16979575/article/details/70169519 通过网上查询,找到一种方案,就是先在o ...

  8. case when then else_啃食Oracle:条件分支表达式CASE

    啃食Oracle:条件分支表达式CASE CASE表达式是条件分支表达式,类似于if - elsif -else条件分支语句.常见用法是在select的表达式列表中使用. 以下图示来自于官方文档 上图 ...

  9. python列表解析,生成表达式(一分钟读懂)

    如果想通过操作和处理一个序列来创建一个新的列表时,可以使用列表解析和生成表达式 区分就是 [ ] ( ) 1.列表解析 list comprehensions 2.生成表达式 a = [ i for ...

最新文章

  1. unity项目警告之 LF CRLF问题
  2. 手机访问PC网站自动跳转到手机网站代码
  3. pregquote php,PHP: preg_quote - Manual
  4. 求 s=a+aa+ aaa+ aaaa +aaaaa+........的值,a是从键盘输入的,项数也为键盘输入
  5. 分享一下:推荐一个网站,练习CSS3
  6. Cortex-M3的整体风景
  7. php输入多少数值自动乘,报表数据填报中的自动计算
  8. 最硬核Visual AssistX 安装破解(2019最新 通用)内含破解原理
  9. 女生不能学理科?python+数据可视化分析15万考生的成绩,刷新了我的认知
  10. Web — 调色盘打开+div
  11. iOS - LocalNotification
  12. 最新版国庆头像生成器微信小程序源码
  13. 数据结构与算法_01链表
  14. 金彩教育:权重提升方法
  15. Decorate 装饰器应用
  16. 必备技能:图解用电烙铁焊接电路
  17. ANOVA与机器学习
  18. FineReport的数据决策系统注册
  19. 顺丰丰桥电子面单打印接口,适用于第三方系统对接
  20. react函数式组件传值之父传子

热门文章

  1. 手机wap前端开发经验
  2. 华为mate10pro计算机设置,华为mate10 pro双清教程,怎么进recovery清理数据和恢复出厂...
  3. 4. Categorical Encoding with CatBoost Encoder
  4. 深度学习项目二: 图像的风格迁移和图像的快速风格迁移 (含数据和所需源码)
  5. 头条号nx配置文件mysql_后端开辟必备的MySQL日记文件知识点
  6. Generator 函数的详解:
  7. socket基础知识以及各种使用场景
  8. IT 2018总结:足迹第五十三步我的同学网聊收获(好口才的十一条铁则)
  9. 我学习Android的一些套路
  10. Scheduler: Initial job has not accepted any resources; check your cluster UI to ensure that workers