Oracle 解析cron定时表达式


版权声明:本文为博主原创文章,未经博主允许不得转载。 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,1112都满足。
, : 用来表示枚举值,例如在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) then
    raise_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 varchar2
  as
  result_str varchar2(128);
  begin
    result_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 varchar2
  as
  result_str varchar2(128);
  begin
    result_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 is
  temp_result_array cron_type_number;
  num number default 1;
  temp_array_1 cron_type_array;--临时数组1
  temp_array_2 l_cron_type_number;--临时数组2
  begin
    temp_result_array := cron_type_number();
    temp_array_1 := cron_get_str(str);
    for i in 1..temp_array_1.count loop
      temp_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 loop
        temp_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 is
  temp_result_array cron_type_array;
  begin
    for i in 1..regexp_count(str,'[^,]+')+1 loop
      temp_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 is
  temp_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;
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值