カレンダーの土日祝日を考慮して、営業日ベースの日付計算をする(支払期日、出荷日など)

kintoneで業務システム作るときには、

「締め日」に「支払サイト」の日数を加算して「支払期日」を算出

なんて処理をよく作ると思います。

 

そのときに、支払期日が休日(土日・祝日)だったら、

直前の平日を指定したい、なんて要件がけっこうあるんじゃないかと。

そんなとき、

・祝日をどう判定するのか?

・祝日の前日がまた祝日だったら?

辺りも考慮する必要があって、ちょっと厄介です。

 

Cybozu CDNでは「moment.js」や「UltraDate.js」が紹介されてますが、

もっとシンプルに書ける「date-fns」と「holiday_jp-js」の組み合わせで実装してみたのんで、紹介します。

 

https://date-fns.org/docs

https://github.com/holiday-jp/holiday_jp-js

 

ライブラリの準備

date-fnsはCDNが公開されてます。

https://cdnjs.com/libraries/date-fns

 

holiday-jpはこいつをダウンロード。

https://raw.githubusercontent.com/holiday-jp/holiday_jp-js/master/release/holiday_jp.min.js

 

kintoneアプリの初期設定

こんな風に、数値1つ、日付3つのシンプルなアプリを作り、

フィールド名=フィールドコードにしておきます。

(入力禁止制約はJSで作ります)

 

JSカスタマイズの設定は、

自作の「latest-weekday.js」と合わせて、こんな風に設定します。

JSを書く

「latest-weekday.js」の中身はこちら。

一番ポイントは「latestWeekday()」関数で、再帰的に土日 or 祝日を判定しております。

(function() {
'use strict';

// 直前の平日を取得
const latestWeekday = function(date) {
if (dateFns.isWeekend(date) || holiday_jp.isHoliday(date)) {
const prevDate = dateFns.subDays(date, 1);
return latestWeekday(prevDate);
}
return date;
};

// 日付を加算
const addDays = function(date, days) {
return dateFns.addDays(date, days);
};

// 支払期日を計算
const payDate = function(cutoffDate, terms) {
const payDateBase = addDays(cutoffDate, terms);
return latestWeekday(payDateBase);
};

// kintoneの日付フィールド向けフォーマット
const formatDate = function(date) {
return dateFns.format(date, 'YYYY-MM-DD');
};

kintone.events.on(['app.record.create.submit', 'app.record.edit.submit', 'app.record.index.edit.submit'], function(event) {
const record = event.record;
record.単純加算.value = formatDate(addDays(record.締め日.value, record.支払サイト.value));
record.支払期日.value = formatDate(payDate(record.締め日.value, record.支払サイト.value));
return event;
});

kintone.events.on(['app.record.create.show', 'app.record.edit.show', 'app.record.index.edit.show'], function(event) {
const record = event.record;
record.単純加算.disabled = record.支払期日.disabled = true;
return event;
});
})();

 

結果

2019年は、4/27〜5/6まで10連休でした。

20日後の日付がその範囲に被ると、直線の平日4/26まで戻してくれているのがわかると思います!

ライブラリについて

祝日を扱うライブラリは他にも色々ありますが、

個人的には色々な機能が盛りだくさんなライブラリよりも

「この日は祝日か否か」だけtrue/falseで返してくれれば十分なので、

holiday_jp-jsが好きなのです。

https://qiita.com/the_red/items/43c7f88ccbba001a1a95

 

moment.jsよりdate-fnsが素晴らしい理由は、

この記事にわかりやすくまとまってます。

https://www.webprofessional.jp/date-fns-javascript-date-library/

momentが予期しない挙動をして「ンガー!」ってなった経験ある方、

乗り換えてみると幸せになれるかも!

 

ちなみに「直後の平日」にすることも簡単にできます。

上記のコードのlatestWeekday()関数を、nextWeekday()関数として

こんな風に変更してやればOK

 

// 直後の平日を取得
const nextWeekday = function(date) {
if (dateFns.isWeekend(date) || holiday_jp.isHoliday(date)) {
const nextDate = dateFns.addDays(date, 1);
return nextWeekday(nextDate);
}
return date;
};

 

変わってるのは実質ここだけです。

const prevDate = dateFns.subDays(date, 1);

「1日前」じゃなくて「1日後」を計算してやればOK。

const nextDate = dateFns.addDays(date, 1);

 

あとは支払期日を計算するpayDate関数内で、nextWeekdayを呼ぶように変更すれば完成です。

  // 支払期日を計算
  const payDate = function (cutoffDate, terms) {
    const payDateBase = addDays(cutoffDate, terms);
    return nextWeekday(payDateBase);
  };

 

結果、こうなります!

赤座様

 

お世話になっております。

手順のとおり進めているのですが

「Uncaught ReferenceError: dateFns is not defined」となってしまいます。

原因をご教授いただけませんでしょうか?

よろしくお願いいたします。

 

青山さま

JavaScriptの設定画面で、date-fnsのURLを設定されていないか、

URLが間違っているのではないでしょうか?

 

date-fnsのJavaScriptのURL(一度ダウンロードしてkintoneにアップロードでもOK)

https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.min.js

 

holiday-jpは、ここからファイルをダウンロードして、kintoneにアップロード

https://raw.githubusercontent.com/holiday-jp/holiday_jp-js/master/release/holiday_jp.min.js

 

上記2つに加えて、自作のJavaScriptを設定する必要があります。

赤座さま

 

お世話になっております。

念のためdate-fnsのJavaScriptのURLを張りなおしました。

holiday-jpに関してもダウンロードしファイルとしてアップしなおしました。

残すは自作のJavaScriptをとなりますが、

新しいアプリを作成し数値フィールドで「支払サイト」日付フィールドで「締め日」、「単純加算」、「支払期日」の3つ

フィールド名=フィールドコードにし設定しましたが同様のエラーが発生します。

今回投稿のご指定いただいている環境であれば「latest-weekday.js」の中身の変更は不要という認識でよろしいでしょうか?

 

また変更が必要な項目があればご教示いただきたく思います。

よろしくお願いします。

赤座 久樹

お世話になります!

こちらのJSを使用させて頂き大変便利に利用できているのですが

休日直前から直後の平日に変更したく

記述通りにやってみたのですが

保存ボタンが押せなくなってしまい、もとのJSに戻すと保存ができる状況です。

以下保存ができないJSですがどこか間違いがありますでしょうか?

恐れ入りますが、ご確認いただけますと幸いです。

 

(function() {
  ‘use strict’;

  // 直後の平日を取得
  const nextWeekday = function(date) {
    if (dateFns.isWeekend(date) || holiday_jp.isHoliday(date)) {
      const nextDate = dateFns.addDays(date, 1);
      return nextWeekday(nextDate);
    }
    return date;
  };

  // 日付を加算
  const addDays = function(date, days) {
    return dateFns.addDays(date, days);
  };

  // 支払期日を計算
  const payDate = function(cutoffDate, terms) {
    const payDateBase = addDays(cutoffDate, terms);
    return latestWeekday(payDateBase);
  };

  // kintoneの日付フィールド向けフォーマット
  const formatDate = function(date) {
    return dateFns.format(date, ‘YYYY-MM-DD’);
  };

  kintone.events.on([‘app.record.create.submit’, ‘app.record.edit.submit’, ‘app.record.index.edit.submit’], function(event) {
    const record = event.record;
    record.単純加算.value = formatDate(addDays(record.注文日.value, record.出荷サイト.value));
    record.出荷予定日.value = formatDate(payDate(record.注文日.value, record.出荷サイト.value));
    return event;
  });

  kintone.events.on([‘app.record.create.show’, ‘app.record.edit.show’, ‘app.record.index.edit.show’], function(event) {
    const record = event.record;
    record.単純加算.disabled = record.出荷予定日.disabled = true;
    return event;
  });
})();

 

 

Riorioさん

使っていただいてありがとうございます!見てみました。

 

latestWeekday()関数をnextWeekday()という名前に変更されているようですが、

支払期日を計算するpayDate()関数内では、もとのlatestWeekday()の名前のまま呼んでいるので、

そこで「latestWeekday is not defined(latestWeekday関数は定義されていません)」

というエラーが出ていますね。

 

payDate()内でもnextWeekday()に変更すればうまくいくと思いますよ!

すみません!

latestWeekdayをnextWeekdayに変えるサンプルは、

僕が自分でコメントして書いていたんですね。すっかり忘れていましたw

 

初心者向けには不親切だったので、payDate関数内を変更する部分も追記しました。

赤座 久樹

お世話になります!

無事できました。ありがとうございます!

大変申し訳ないのですがここのスレッドでのご質問失礼いたします。

弊社の場合製造をしており、土日祝前日の出荷サイト6日パターンで大丈夫だったのですが

注文日の翌日を1日目とカウントして6営業日を注文者様への到着日とする業務の流れに変更となり、今回土日祝日後日の出荷サイトパターンにJSを変更いたしました。

テストで確認をしたところ

例えば今年の4月でいうと暦通りの営業で

4/25注文の場合26、27、28、5/2、5/6、5/9というように5/9が到着日となります。

今までは単に土日祝の到着になる場合は、休日の前に出荷をしてお客様に到着するパターンでよかったのですが

6営業日となり祝日着をなくすことで、ずれるところが出てきました。

出荷サイト(貴殿記述は支払いサイト)を7日に設定し、土日だけであれば問題ないことが分かったのですが

上記のパターンで行くと

4/25の注文品の着日5/9が6営業日になります=5/6の出荷ということになります。

JSのパターンをどのようにしたらうまくいくのでしょうか?

恐れ入りますが、お分かりになりましたら

ご教授いただけますと幸いです。

「5営業日後に出荷、6営業日後に到着」

ってことで間違いないですかね?

 

もともとは「単純加算したあと、一番近い営業日を見つける」というプログラムなので、

「2営業日以上ずらす」ことは想定していませんでした。

でも一般的に需要がありそうで面白いので、作ってみました。大サービスですよw

 

プログラム全文(business-days.js)

「date-fns」と「holiday_jp-js」は変わらず使います。

(function () {
  'use strict';

  // 直後の平日を取得
  const nextWeekday = function (date) {
    if (dateFns.isWeekend(date) || holiday_jp.isHoliday(date)) {
      const nextDate = dateFns.addDays(date, 1);
      return nextWeekday(nextDate);
    }
    return date;
  };

  // 日付を加算
  const addDays = function (date, days) {
    return dateFns.addDays(date, days);
  };

  // X営業日後を計算
const businessDays = function (date, days) {
  let result = new Date(date);
    for (let i = 0; i < days; i++) {
    const nextDay = addDays(result, 1);
    result = nextWeekday(nextDay);
    }
    return result;
  };

  // kintoneの日付フィールド向けフォーマット
  const formatDate = function (date) {
    return dateFns.format(date, 'YYYY-MM-DD');
  };

  kintone.events.on(
    ['app.record.create.submit', 'app.record.edit.submit', 'app.record.index.edit.submit'],
    function (event) {
      const record = event.record;
      record.出荷日.value = formatDate(businessDays(record.注文日.value, Number(record.出荷サイト.value)));
      record.到着日.value = formatDate(businessDays(record.注文日.value, Number(record.出荷サイト.value) + 1));
      return event;
    }
  );

  kintone.events.on(['app.record.create.show', 'app.record.edit.show', 'app.record.index.edit.show'], function (event) {
    const record = event.record;
    record.出荷日.disabled = record.到着日.disabled = true;
    return event;
  });
})();

 

payDate関数は廃止して、businessDaysという関数を新設しました。

forループを回すことで「X営業日後」を計算できるようにしています。

kintoneに「出荷サイト5営業日後」と入れておけば、

到着日はJSの中で+1して計算するようにしてみました。

 

いかがでしょ??

赤座 久樹

お世話になります!

ありがとうございます!

連休がある月で何パターンか試しまして

理想通りの動きになりました!

8/1から実装できたらうれしいといわれていたので

大変感謝感謝です。

こちらで本番環境も進めてみます!

良かったです:+1:

このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。