Oracle计算当前日期在N个工作日之后的日期

最近项目中有一个自动扣款的需求,需要在商品出售之后 7 个工作日之后进行自动扣款,由此封装了一个存储过程以便于调用。

注意:本示例使用oracle。

一、创建一张GGHOLIDAY表并insert部分数据

①表中holiday字段长度为31,holiday字段:W-正常周末  H-全天假期  D-半天假期 X-这个月不存在这天 .-正常工作日'。

-- Create table
create table GGHOLIDAY
(
  id          VARCHAR2(32) not null,
  year        VARCHAR2(12),
  month       VARCHAR2(12),
  holiday     VARCHAR2(31),
  creatorcode VARCHAR2(10),
  createtime  DATE,
  updatercode VARCHAR2(10),
  updatetime  DATE,
  validdate   DATE,
  invaliddate DATE,
  validind    VARCHAR2(1) not null,
  remark      VARCHAR2(4000)
)
tablespace USERS
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );
-- Add comments to the table 
comment on table GGHOLIDAY
  is '节假日';
-- Add comments to the columns 
comment on column GGHOLIDAY.id
  is 'id';
comment on column GGHOLIDAY.year
  is '年份';
comment on column GGHOLIDAY.month
  is '月份';
comment on column GGHOLIDAY.holiday
  is '节假日标志,31位代表31天 格式: W-正常周末  H-全天假期  D-半天假期 X-这个月不存在这天 .-正常工作日';
comment on column GGHOLIDAY.creatorcode
  is '创建人';
comment on column GGHOLIDAY.createtime
  is '创建时间';
comment on column GGHOLIDAY.updatercode
  is '最后修改人';
comment on column GGHOLIDAY.updatetime
  is '最后修改时间';
comment on column GGHOLIDAY.validdate
  is '生效日期';
comment on column GGHOLIDAY.invaliddate
  is '失效日期';
comment on column GGHOLIDAY.validind
  is '有效标志含义:1-有效;0-无效';
comment on column GGHOLIDAY.remark
  is '备注';
-- Create/Recreate primary, unique and foreign key constraints 
alter table GGHOLIDAY
  add constraint PK_GGHOLIDAY primary key (ID)
  using index 
  tablespace USERS
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 512K
    next 512K
    minextents 1
    maxextents unlimited
  );

②在GGHOLIDAY表中添加2019年8月、9月、10月、11月、12月的节假日信息。若需更多,自行配置。

insert into ggholiday (ID, YEAR, MONTH, HOLIDAY, CREATORCODE, CREATETIME, UPDATERCODE, UPDATETIME, VALIDDATE, INVALIDDATE, VALIDIND, REMARK)
values ('4028d0816980aba8016980c355ef0011', '2019', '08', '..WW.....WW.....WW.....WW.....W', '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), null, null, '1', null);

insert into ggholiday (ID, YEAR, MONTH, HOLIDAY, CREATORCODE, CREATETIME, UPDATERCODE, UPDATETIME, VALIDDATE, INVALIDDATE, VALIDIND, REMARK)
values ('4028d0816980aba8016980c355ef0010', '2019', '09', 'W.....WW....HHH.....WW.....W..X', '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), null, null, '1', null);

insert into ggholiday (ID, YEAR, MONTH, HOLIDAY, CREATORCODE, CREATETIME, UPDATERCODE, UPDATETIME, VALIDDATE, INVALIDDATE, VALIDIND, REMARK)
values ('4028d0816980aba8016980c355ef0001', '2019', '10', 'HHHHHHH.....W.....WW.....WW....', '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), null, null, '1', null);

insert into ggholiday (ID, YEAR, MONTH, HOLIDAY, CREATORCODE, CREATETIME, UPDATERCODE, UPDATETIME, VALIDDATE, INVALIDDATE, VALIDIND, REMARK)
values ('4028d0816980aba8016980c355ef0012', '2019', '11', '.WW.....WW.....WW.....WW.....WX', '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), null, null, '1', null);

insert into ggholiday (ID, YEAR, MONTH, HOLIDAY, CREATORCODE, CREATETIME, UPDATERCODE, UPDATETIME, VALIDDATE, INVALIDDATE, VALIDIND, REMARK)
values ('4028d0816980aba8016980c355ef0013', '2019', '12', 'W.....WW.....WW.....WW.....WW..', '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), '100000001', to_date('15-03-2019 17:51:00', 'dd-mm-yyyy hh24:mi:ss'), null, null, '1', null);

二、编写存储过程计算节假日:

1、计算某个日期 N 个工作日之后的日期:

代码:

-- 计算传入日期之后v_days个工作日之后的日期,不支持跨2个月的操作
  FUNCTION getWorkDate(v_nowDatestr VARCHAR2, v_days NUMBER) RETURN DATE IS
    v_returnDate DATE;
    v_nowDate DATE; --当前时间

    v_strNowMonthHOLIDAY VARCHAR2(31); -- 当月v_nowDate后的节假日及工作日标识字符串
    v_strNextMonthHOLIDAY VARCHAR2(31); -- 下月节假日及工作日字符串
    v_nextDays NUMBER; -- 下月需要计算的工作日

    v_nowDays NUMBER;  -- 当前是本月第几天
    v_nowMonthWorkDay NUMBER; -- 当月剩余工作日天数

    v_realyDays NUMBER DEFAULT 0; -- v_nowDate到v_days个工作日的实际天数

    v_workDaysFlag NUMBER DEFAULT 0;-- 循环时记录这是第几个工作日
  BEGIN
    v_nowDate := to_date(v_nowDatestr,'yyyy-MM-dd');
    v_nowDays := to_number(to_char(v_nowdate,'dd'))+1;

    -- 获取当月v_nowDate日期之后的节假日及工作日标识字符串
    SELECT replace(substr(T.HOLIDAY,v_nowDays,31-v_nowDays),'X','')
      INTO V_STRNOWMONTHHOLIDAY
      FROM GGHOLIDAY T
     WHERE T.YEAR = TO_CHAR(V_NOWDATE, 'yyyy')
       AND T.MONTH = TO_CHAR(V_NOWDATE, 'MM');

    SELECT (LENGTHB(V_STRNOWMONTHHOLIDAY) -
       LENGTHB(REPLACE(V_STRNOWMONTHHOLIDAY, '.', '')))
       INTO v_nowMonthWorkDay
    FROM DUAL;

    IF v_nowMonthWorkDay >= v_days THEN
      -- 如果当月v_nowDate日期之后的工作日还多于v_days天
      FOR i IN 1..length(v_strNowMonthHOLIDAY) LOOP
        IF substr(v_strNowMonthHOLIDAY,i,1)='.' THEN
          v_workDaysFlag := v_workDaysFlag + 1;
        END IF;
        v_realyDays := v_realyDays + 1;
        IF v_workDaysFlag = v_days THEN
          GOTO NEXT;
        END IF;
      END LOOP;
      <> NULL;
    ELSE
      -- 如果当月v_nowDate日期之后的工作日还少于或等于v_days天,就需要推迟到下个月
      v_nextDays := v_days-v_nowMonthWorkDay;
      --v_realyDays:= v_nextDays;
      -- 获取下月节假日及工作日标识字符串
      SELECT replace(T.HOLIDAY,'X','')
        INTO v_strNextMonthHOLIDAY
        FROM GGHOLIDAY T
       WHERE T.YEAR = TO_CHAR(add_months(v_nowDate,1), 'yyyy')
         AND T.MONTH = TO_CHAR(add_months(v_nowDate,1), 'MM');

      FOR i IN 1..length(v_strNextMonthHOLIDAY) LOOP
        IF substr(v_strNextMonthHOLIDAY,i,1)='.' THEN
          v_workDaysFlag := v_workDaysFlag + 1;
        END IF;
        v_realyDays := v_realyDays + 1;
        IF v_workDaysFlag = v_nextDays THEN
          GOTO NEXTSTR;
        END IF;
      END LOOP;
      <> NULL;
      v_realyDays := v_realyDays + length(v_strNowMonthHOLIDAY);
    END IF;
    v_returnDate := v_nowDate + v_realyDays;
    RETURN v_returnDate;
  END getWorkDate;

2、计算某个日期之前 N 个工作日的日期:

  -- 获取 V_NOWDATEstr 日期前 V_DAYS 个工作日的日期
  PROCEDURE GETBEFOREWORKDATE(V_NOWDATEstr  IN VARCHAR2,
                              V_DAYS        IN NUMBER,
                              V_MESSAGECODE OUT VARCHAR2,
                              V_RETURNDATE  OUT DATE) IS
    v_nowDate DATE;
    v_strNowMonthHOLIDAY VARCHAR2(31); -- 当月v_nowDate前的节假日及工作日标识字符串
    v_strLastMonthHOLIDAY VARCHAR2(31); -- 上月节假日及工作日字符串
    v_lastDays NUMBER; -- 上月需要计算的工作日

    v_nowDays NUMBER;  -- 当前是本月第几天
    v_nowMonthWorkDay NUMBER; -- 当月剩余工作日天数

    v_realyDays NUMBER DEFAULT 0; -- v_nowDate到v_days个工作日的实际天数

    v_workDaysFlag NUMBER DEFAULT 0;-- 循环时记录这是第几个工作日
  BEGIN
    v_nowDate := to_date(v_nowDatestr,'yyyy-MM-dd');
    v_nowDays := to_number(to_char(v_nowdate,'dd'))-1;

    -- 获取当月v_nowDate日期之前的节假日及工作日标识字符串
    SELECT replace(substr(T.HOLIDAY,0,v_nowDays),'X','')
      INTO V_STRNOWMONTHHOLIDAY
      FROM GGHOLIDAY T
     WHERE T.YEAR = TO_CHAR(V_NOWDATE, 'yyyy')
       AND T.MONTH = TO_CHAR(V_NOWDATE, 'MM');

    SELECT (LENGTHB(V_STRNOWMONTHHOLIDAY) -
       LENGTHB(REPLACE(V_STRNOWMONTHHOLIDAY, '.', '')))
       INTO v_nowMonthWorkDay
    FROM DUAL;

    IF v_nowMonthWorkDay >= v_days THEN
      -- 如果当月v_nowDate日期之后的工作日还多于v_days天
      FOR i IN 1..length(v_strNowMonthHOLIDAY) LOOP
        IF substr(v_strNowMonthHOLIDAY,length(v_strNowMonthHOLIDAY)-i+1,1)='.' THEN
          v_workDaysFlag := v_workDaysFlag + 1;
        END IF;
        v_realyDays := v_realyDays + 1;
        IF v_workDaysFlag = v_days THEN
          GOTO NEXT;
        END IF;
      END LOOP;
      <> NULL;
    ELSE
      -- 如果当月v_nowDate日期之后的工作日还少于或等于v_days天,就需要推迟到下个月
      v_lastDays := v_days-v_nowMonthWorkDay;
      -- 获取上月节假日及工作日标识字符串
      SELECT replace(T.HOLIDAY,'X','')
        INTO v_strLastMonthHOLIDAY
        FROM GGHOLIDAY T
       WHERE T.YEAR = TO_CHAR(v_nowDate - interval '1' month, 'yyyy')
         AND T.MONTH = TO_CHAR(v_nowDate - interval '1' month, 'MM');

      FOR i IN 1..length(v_strLastMonthHOLIDAY) LOOP
        IF substr(v_strLastMonthHOLIDAY,length(v_strLastMonthHOLIDAY)-i+1,1)='.' THEN
          v_workDaysFlag := v_workDaysFlag + 1;
        END IF;
        v_realyDays := v_realyDays + 1;
        IF v_workDaysFlag = v_lastDays THEN
          GOTO NEXTSTR;
        END IF;
      END LOOP;
      <> NULL;
      v_realyDays := v_realyDays + length(v_strNowMonthHOLIDAY);
    END IF;
    v_returnDate := v_nowDate - v_realyDays;
  EXCEPTION
    WHEN OTHERS THEN
      V_MESSAGECODE := '000';
  END GETBEFOREWORKDATE;

三、进行测试

1、计算某个日期 N 个工作日之后的日期:

SELECT ggHolidayPackage.getWorkDate('2019-09-21',3) FROM dual;

输出:2019/9/25

2、计算某个日期之前 N 个工作日的日期:

DECLARE
  V_MESSAGECODE  VARCHAR2(10);
  V_RETURNDATE   DATE;
BEGIN
  ggHolidayPackage.GETBEFOREWORKDATE('2019-09-21',3,V_MESSAGECODE,V_RETURNDATE);
  dbms_output.put_line('V_MESSAGECODE='||V_MESSAGECODE);
  dbms_output.put_line('V_RETURNDATE='||to_char(V_RETURNDATE,'yyyy-MM-dd'));
END;

输出:
V_MESSAGECODE=
V_RETURNDATE=2019-09-18

 

 

 

你可能感兴趣的:(oracle,存储过程,工作日,数据库,代码封装)