Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

GASで特定の休日を除いた営業日の計算

解決したいこと

フォームに書かれてある「完成日」を基準とし、その日から3営業日前に「試作日」を特定してコンソール上に表示する機能をGASで作ろうとしています。

営業日とは①土日と②日本の祝日、③会社独自の休日(2022年12月29日~2023年1月4日。google calendarで予定を作成してある)の3つを除いた日と定義し、それぞれ条件分岐で設定しました。

実行するとだいたいうまくいくのですが、例えば2023年1月10日を完成日として起動すると1月4日が試作日として表示されてしまいます(正しくは12月27日)。

条件分岐などが何か間違っているでしょうか(ちなみに③を12月30日~1月6日に設定すると、正しく12月27日が表示されました)。
ご教示くださると幸いです。

コードは下記になります。

function trialDay() {
  var ss_copy = SpreadsheetApp.getActiveSpreadsheet();
  var s_name_from = ss_copy.getSheetByName('form');
  var lastRow= s_name_from.getLastRow();
  var kansei_bi = s_name_from.getRange(lastRow, 1).getValues();

  // 基準日を設定
  const date = new Date(kansei_bi);
  
  // 基準日が属する月の「完成日」を求める
  console.log(Utilities.formatDate(date,'JST','yyyy/MM/dd') + ' です');
  
  // マイナスn営業日である試作期限日を求める
  const minus_day = 3; //マイナス3営業日
  let cnt = 0;
  
  while (cnt < minus_day) { 
    if (!isHoliday_(date)) cnt++;   //休日でないならcntが1進み、dateが一日戻る
    date.setDate(date.getDate()-1);
  }
  
  console.log(Utilities.formatDate(date,'JST','yyyy/MM/dd') + ' が試作期限日です。');
}

/**
* 休日か否かを判定する関数
* 
* @param  {Date}    判定する日付
* @return {boolean} 休日ならtrueを返す
*/
function isHoliday_(date) {
  
  // ①土日の判定  
  const day = date.getDay(); //曜日取得
  if (day === 0 || day === 6) return true;
  
  // ②祝日の判定
  const id = 'ja.japanese#holiday@group.v.calendar.google.com';
  const calendar = CalendarApp.getCalendarById(id);
  const events = calendar.getEventsForDay(date);
  
  if (events.length) return true; //なんらかのイベントがある = 祝日
  
  // ③会社独自の休日判定
  const id2 = '******************@gmail.com'; 
  const calendar2 = CalendarApp.getCalendarById(id2);
  const events2 = calendar2.getEventsForDay(date);
  if (events2.length) return true; //なんらかのイベントがある = 休日

}

0

4Answer

isHoliday_メソッドが正しく動作しているか確認するために、12月と1月の休日が正しく判定されているかどうかを確認してみてください。

isHolidayが正しく動作しているなら、trialDayメソッドのほうにバグがあることがわかるので、
以下の部分で日付がどのようになっているか出力してみてください。

while (cnt < minus_day) { 
  if (!isHoliday_(date)) cnt++;   //休日でないならcntが1進み、dateが一日戻る
  date.setDate(date.getDate()-1);
  // ログ
  console.log(Utilities.formatDate(date,'JST','yyyy/MM/dd'));
}
1Like

Comments

  1. コメントありがとうございます。ご指摘の一文を書いて出力しました。

    カレンダーの12/29~1/5に予定がある状態で、

    1月11日を完成日とすると
    2023/01/11 が完成日です
    2023/01/10
    2023/01/09
    2023/01/08
    2023/01/07
    2023/01/06
    2023/01/05
    2023/01/05 が試作期限日です。

    1月10日を完成日とすると
    2023/01/10 が完成日です
    2023/01/09
    2023/01/08
    2023/01/07
    2023/01/06
    2023/01/05
    2023/01/04
    2023/01/03
    2023/01/02
    2023/01/01
    2022/12/31
    2022/12/30
    2022/12/29
    2022/12/28
    2022/12/27
    2022/12/27 が試作期限日です。

    となりました。11日完成日だと10, 6と来て5日は休日のはずですが誤まりで、10日完成日だと1/6, 12/28,12/27となり合っていることになります。
    いかがでしょうか。

@abiiii さんの補足です。
isHoliday_が正しく動作しているかを確認するのであれば、日付の操作前かつisHoliday_の結果がどう判定されているのかをわかるようにログ出力すると良いです。
こんな感じに。

while (cnt < minus_day) {
  const text = isHoliday_(date) ? '休日です' : '休日じゃないです';
  console.log(Utilities.formatDate(date,'JST','yyyy/MM/dd') + '' + text);

  if (!isHoliday_(date)) cnt++;   //休日でないならcntが1進み、dateが一日戻る
  date.setDate(date.getDate()-1);
}

これを動かしてログを見てみればわかると思いますが、今の処理だと

  1. 「完成日」が営業日のカウントに含まれている
  2. 3営業日前が見つかった前の日を「試作期限日」としている

のが原因です。

例えば2023年1月10日を完成日として起動すると1月4日が試作日として表示されてしまいます

上記のケースだと、1/10, 1/6, 1/5で3営業日カウントされ、1日前に戻ってループを抜けるので1月4日が試作日として計算されることになります。
期待値通りに計算できているケースは、たまたま3営業日前の前日が営業日だったということですね。

1Like

Comments

  1. ご回答ありがとうございます! その通りでした。
    いただいたご指摘を改善して下記のスクリプトにしたところうまくいっている印象です。
    何かお気づきの点、改善点などもしありましたらご教示くださればありがたいです。

    >
    date.setDate(date.getDate()-1); //完成日の一日前から始まるようにしておく。

    while (cnt < minus_day) {
    const text = isHoliday_(date) ? '休日です' : '営業日です';
    console.log(Utilities.formatDate(date,'JST','yyyy/MM/dd') + ' は ' + text);
    if (!isHoliday_(date)) cnt++; //休日でない(=営業日)ならcntが1進む。
    if(cnt >= minus_day) break; //条件を満たした直後にdateがマイナスされるのを防ぐ
    date.setDate(date.getDate()-1);

(回答者コメントだとマークダウンが効かないのでこちらから...)

考え方は合っています!
ただ、少し冗長だったので

while (cnt < minus_day) {
  // 完成日は営業日のカウントに含めない
  date.setDate(date.getDate()-1);
  // 休日でないならcntが1進む
  if (!isHoliday_(date)) cnt++;
}

こんな感じにdateを1日前に戻してからisHoliday_で判定するとスッキリします。

ここからは本筋とは関係ない話になりますが、変数名の決め方や変数宣言(const, let, var)にどれを使うかなどなど、このあたりを参考にするとより読みやすいコードになると思います。
https://tonari-it.com/gas-coding-guide-line/

個人で読み書きする程度ならここまでする必要もないとも思うのですが、一度知っておくと以降書くときに一々書き方で迷わなくなるのでおすすめです。

1Like

Comments

  1. 丁寧にありがとうございました。
    ご参考サイトも役立ちそうです。
    遅くなりましたが御礼まで。

現状のプログラムでは、日付の判定開始が完成日からになるので、完成日から見て3営業日前ではなく2営業日前になります。
さらにループ終了時に余計に日付をマイナスしているため、結果としては2営業日+1日前となります。

完成日が1月11日の場合、
1月11日から判定が始まり、1月6日の判定終了時点で終了条件を満たします。
01月11日 平日 cnt = 1
01月10日 平日 cnt = 2
01月09日 休日 cnt = 2
01月08日 休日 cnt = 2
01月07日 休日 cnt = 2
01月06日 平日 cnt = 3
この時点でcntが3となり終了条件を満たします。ただし、日付をマイナスする処理が残っているため結果的には01月05日となります。

以下のように順番を入れ替えることで正しく動作すると思います。

while (cnt < minus_day) { 
  date.setDate(date.getDate()-1);
  if (!isHoliday_(date)) cnt++;   //休日でないならcntが1進み、dateが一日戻る
}
0Like

Your answer might help someone💌