営業日を判断するOracle PL/SQLパッケージ

現実逃避から少しでもエンジンがかかるように、コードを公開してみよう。

OracleのPL/SQLで書いた営業日/稼働日判定用のストアード・パッケージ。いろいろと調べてみたのだけれど、ぴったりくるものが見当たらなくて書いてみたもの。まだリリースまで時間があって、それほどテストされていないけれど、そんなに複雑でもないので大丈夫ではないかと。IS_WORKDAYがCHARを返すのはカッコ悪いけれど、いろいろあってこういう仕様。直すのは簡単なので気になる人は直してください。

仕様としては、1)土曜日と日曜日は営業日じゃない、2)休日テーブル(holidays)に日付があったらそれは休日。wkd_pkg.is_workdayを直すことで、土曜日は営業日にしたり、水曜日を休日にしたりできるので、その辺りは適当に。仕様はExcelにあるworkdayとnetworkdayを参考にしているけれど、厳密には一緒じゃない(と思う)。

このパッケージには4つの関数があります。1つはオーバーロードしたものなので、実質的には3つ。

1. FUNCTION WORKDAY( i_startdate IN date, i_days IN number) RETURN DATE;
開始日からi_days営業日後の日付を返す、 i_daysが正数なら未来、負数なら過去、開始日は数えない。

2. FUNCTION IS_WORKDAY( i_date IN date) RETURN CHAR;
指定した日付が営業日ならYを返す。営業日じゃなければNを返す。

3. FUNCTION NETWORKDAY( i_startdate IN date, i_enddate IN date) RETURN NUMBER;
開始日と終了日の間に含まれる営業日の日数を返す。日数には開始日と終了日も含まれる。

コードはGistにおいてあります。 Gist: 294489

CREATE OR REPLACE PACKAGE WKD_PKG AS
/*
 営業日パッケージ

 ※土曜、日曜が営業日の場合は、IS_WORKDAYを直す
 ※国民の祝日が営業日の場合は、休日テーブルを直す

-- 休日テーブル(祝祭日、お盆とか、都民の日とかも)
create table holidays (
    holiday date,
    primary key(holiday)
);
-- 2010年の国民の祝日を入れておく
-- ここが便利 http://cbdb.cybozu.co.jp/cgi-bin/db.cgi?page=DBView&did=690
insert into holidays values(to_date('2010-01-01','YYYY-MM-DD')); --元日
insert into holidays values(to_date('2010-01-11','YYYY-MM-DD')); --成人の日
insert into holidays values(to_date('2010-02-11','YYYY-MM-DD')); --建国記念の日
insert into holidays values(to_date('2010-03-21','YYYY-MM-DD')); --春分の日
insert into holidays values(to_date('2010-03-22','YYYY-MM-DD')); --振替休日
insert into holidays values(to_date('2010-04-29','YYYY-MM-DD')); --昭和の日
insert into holidays values(to_date('2010-05-03','YYYY-MM-DD')); --憲法記念日
insert into holidays values(to_date('2010-05-04','YYYY-MM-DD')); --みどりの日
insert into holidays values(to_date('2010-05-05','YYYY-MM-DD')); --こどもの日
insert into holidays values(to_date('2010-07-19','YYYY-MM-DD')); --海の日
insert into holidays values(to_date('2010-09-20','YYYY-MM-DD')); --敬老の日
insert into holidays values(to_date('2010-09-23','YYYY-MM-DD')); --秋分の日
insert into holidays values(to_date('2010-10-11','YYYY-MM-DD')); --体育の日
insert into holidays values(to_date('2010-11-03','YYYY-MM-DD')); --文化の日
insert into holidays values(to_date('2010-11-23','YYYY-MM-DD')); --勤労感謝の日
insert into holidays values(to_date('2010-12-23','YYYY-MM-DD')); --天皇誕生日

*/

/*
|| 開始日からi_days営業日後の日付を返す
|| i_daysが正数なら未来、負数なら過去
|| ※開始日は数えない
*/
 FUNCTION WORKDAY( i_startdate IN date, i_days IN number) RETURN DATE;

/*
|| 指定した日付が営業日ならYを返す。営業日じゃなければNを返す。
*/
 FUNCTION IS_WORKDAY( i_date IN date) RETURN CHAR;
 -- 今日が営業日かどうか
 FUNCTION IS_WORKDAY RETURN CHAR;

/*
|| 開始日と終了日の間に含まれる営業日の日数を返す。
|| 日数には開始日と終了日も含まれる。
*/
 FUNCTION NETWORKDAY( i_startdate IN date, i_enddate IN date) RETURN NUMBER;
END WKD_PKG;
/

----------------------------------------------------------
--    PACKAGE BODY
----------------------------------------------------------
CREATE OR REPLACE PACKAGE BODY WKD_PKG AS

FUNCTION WORKDAY( i_startdate IN date, i_days IN number) RETURN DATE
IS
  i   NUMBER := 0;
  work_days NUMBER := 0;
BEGIN
  IF i_days = 0 THEN
     RETURN i_startdate;
  END IF;

  WHILE true LOOP
    IF i_days > 0 THEN
       i := i + 1;
    ELSE
       i := i - 1;
    END IF;

    IF IS_WORKDAY(i_startdate + i) = 'Y' THEN
      work_days := work_days + 1;
    END IF;

    IF abs(work_days) = abs(i_days) THEN
       exit;
    END IF;
  END LOOP;

  RETURN i_startdate + i;
END;

FUNCTION IS_WORKDAY(i_date IN date) RETURN CHAR
IS
  hol NUMBER := 0;
BEGIN
  -- 日曜日:1と土曜日:7に含まれない
  IF TO_CHAR(i_date, 'D') NOT IN ('1', '7') THEN
     -- 土日以外でholidaysに存在しなければ営業日
     SELECT COUNT(*) INTO hol FROM holidays WHERE holiday=trunc(i_date);
     IF hol = 0 THEN
        RETURN 'Y';
     END IF;
  END IF;
  RETURN 'N';
END;

FUNCTION IS_WORKDAY RETURN CHAR
IS
BEGIN
  RETURN IS_WORKDAY(SYSDATE);
END;


FUNCTION NETWORKDAY( i_startdate IN date, i_enddate IN date) RETURN NUMBER
IS
  i number := 0;
  diff number := 0;
  days number := 0;
BEGIN
  diff := trunc(i_enddate) - trunc(i_startdate);
  FOR i IN 0..diff LOOP
      IF IS_WORKDAY(i_startdate + i) = 'Y' THEN
        days := days + 1;
      END IF;
  END LOOP;
  RETURN days;
END;

END WKD_PKG;
/

/*
-- テスト

-- 適当に
SELECT to_char(WKD_PKG.workday(to_date('2010-01-09','YYYY-MM-DD'), 2),'YYYY-MM-DD') FROM dual;
SELECT to_char(WKD_PKG.workday(to_date('2010-01-08','YYYY-MM-DD'), 4),'YYYY-MM-DD') FROM dual;
SELECT to_char(WKD_PKG.workday(to_date('2010-01-07','YYYY-MM-DD'), 4),'YYYY-MM-DD') FROM dual;
SELECT to_char(WKD_PKG.workday(to_date('2010-01-07','YYYY-MM-DD'), -4),'YYYY-MM-DD') FROM dual;


-- 全部6のはず。9,10が土日。11日が祝日。
SELECT WKD_PKG.NETWORKDAY(to_date('2010-01-07','YYYY-MM-DD'),to_date('2010-01-15','YYYY-MM-DD')) FROM dual;
SELECT WKD_PKG.NETWORKDAY(to_date('2010-01-07','YYYY-MM-DD'),to_date('2010-01-16','YYYY-MM-DD')) FROM dual;
SELECT WKD_PKG.NETWORKDAY(to_date('2010-01-07','YYYY-MM-DD'),to_date('2010-01-17','YYYY-MM-DD')) FROM dual;
-- 7のはず。9,10が土日。11日が祝日。
SELECT WKD_PKG.NETWORKDAY(to_date('2010-01-07','YYYY-MM-DD'),to_date('2010-01-18','YYYY-MM-DD')) FROM dual;

*/

追記:GitHubがトラブってた。なんだかAnonymousのGistになっていたのでForkしたGistに変えました。