GASで第1営業日・最終営業日に定期予定を作成する方法

記事タイトルとURLをコピーする

G-gen の高宮です。Google Apps Script(GAS)を用いた Google カレンダーの自動化について解説します。特に、土日祝日や会社休日を考慮した月の第1営業日や最終営業日といった、変則的な日付への定期的な予定追加に焦点を当てます。

はじめに

概要

Google カレンダーでは月ごとの繰り返しの予定を、「毎月25日」や「毎月第3金曜日」などの形式で作成することはできます。しかし、土日祝日・会社休日などを考慮した会社営業日での定期的なスケジュール作成を簡単に行うことはできません。

会社の業務では、月の第1営業日に前月の勤怠の締め作業を依頼したり、月の最終営業日に経費精算の申請をするなどのケースがあります。そのような場合に、手間をかけずに Google カレンダーに定期的な予定を作成したいです。

当記事では、GAS を使用して、土日祝日・会社休日を考慮し、月の第1営業日や最終営業日に Google カレンダーに定期的な予定を追加する具体的な方法を解説します。

Google Apps Script(GAS)とは

Google Apps Script(GAS)は、Google Workspace と統合されたアプリケーションを簡単に作成できるアプリケーション開発プラットフォームです。JavaScript でコードを記述し、Gmail、カレンダーなどの Google Workspace アプリ用の組み込みライブラリを使用できます。

新しい GAS プロジェクトの作成

以下の URL にアクセスすると、 Apps Script ダッシュボードが表示されます。

https://script.google.com/

新しいプロジェクト作成ボタンを押下すると、新しい GAS プロジェクトを作成できます。

営業日計算処理

実装

プロジェクトにファイル date_utils.gs を作成し、第1営業日と最終営業日を計算する処理を実装します。今回の例では、土日、祝日と年末年始に休暇がある会社を想定しています。

/**
 * 会社休日(月/日形式の文字列配列)
 * この配列に、固定の会社休日を追加できます。
 */
const COMPANY_HOLIDAYS = ['12/30', '12/31', '1/1', '1/2', '1/3', '1/4'];
 
/**
 * 日本の祝日カレンダーを取得します。
 * 繰り返し呼び出されるのを防ぐため、一度だけ取得してキャッシュします。
 */
const JAPANESE_HOLIDAY_CALENDAR = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com');
 
/**
 * 指定された日付が休日かどうかを判定します。
 * @param {Date} date - 判定対象の日付オブジェクト。
 * @return {boolean} - 休日の場合は true、営業日の場合は false を返します。
 */
function isHoliday(date) {
  // 1. 土曜日(6)または日曜日(0)か判定
  const day = date.getDay();
  if (day === 0 || day === 6) {
    return true;
  }
 
  // 2. 日本の祝日か判定
  const events = JAPANESE_HOLIDAY_CALENDAR.getEventsForDay(date);
  if (events.length > 0) {
    return true;
  }
 
  // 3. 会社休日か判定
  const month = date.getMonth() + 1; // getMonth()は0始まりのため+1
  const dateOfMonth = date.getDate();
  const dateString = `${month}/${dateOfMonth}`;
  if (COMPANY_HOLIDAYS.includes(dateString)) {
    return true;
  }
 
  // 上記のいずれにも該当しない場合は営業日
  return false;
}
 
/**
 * 指定された年月の第1営業日を取得します。
 * @param {number} year - 年(例: 2025)。
 * @param {number} month - 月(1〜12)。
 * @return {Date} - 第1営業日の日付オブジェクト。
 */
function getFirstBusinessDay(year, month) {
  // 指定された月の1日を開始日として設定
  const date = new Date(year, month - 1, 1); // 月は0始まりのため-1
 
  // 休日でなくなるまで日付を1日ずつ進める
  while (isHoliday(date)) {
    date.setDate(date.getDate() + 1);
  }
  return date;
}
 
/**
 * 指定された年月の最終営業日を取得します。
 * @param {number} year - 年(例: 2025)。
 * @param {number} month - 月(1〜12)。
 * @return {Date} - 最終営業日の日付オブジェクト。
 */
function getLastBusinessDay(year, month) {
  // 指定された月の末日を開始日として設定
  const date = new Date(year, month, 0); // 翌月の0日目を指定すると、その月の末日が取得できる
 
  // 休日でなくなるまで日付を1日ずつ遡る
  while (isHoliday(date)) {
    date.setDate(date.getDate() - 1);
  }
  return date;
}

ここでは、Google カレンダーで提供されている「日本の祝日」カレンダーから祝日の情報を取得するため、CalendarApp クラスを使用しています。

動作確認

新しいファイル コード.gs に以下の処理を実装し、main 関数を実行し営業日の計算が正常に実行できているか確認します。

/**
 * テスト用の対象年月のリスト
 */
const TARGET_YEAR_MONTH_LIST = [
  {
    // 2024年12月
    TARGET_YEAR: 2024,
    TARGET_MONTH: 12,
  },
  {
    // 2025年1月
    TARGET_YEAR: 2025,
    TARGET_MONTH: 1,
  },
]
 
/**
 * 実行テスト用のメイン関数
 */
function main() {
 
  TARGET_YEAR_MONTH_LIST.forEach(targetYearMonth => {
    const firstDay = getFirstBusinessDay(targetYearMonth.TARGET_YEAR, targetYearMonth.TARGET_MONTH);
    const lastDay = getLastBusinessDay(targetYearMonth.TARGET_YEAR, targetYearMonth.TARGET_MONTH);
    // 結果をログに出力
    console.log(`${targetYearMonth.TARGET_YEAR}${targetYearMonth.TARGET_MONTH}月の第1営業日: ${Utilities.formatDate(firstDay, 'JST', 'yyyy-MM-dd (E)')}`);
    console.log(`${targetYearMonth.TARGET_YEAR}${targetYearMonth.TARGET_MONTH}月の最終営業日: ${Utilities.formatDate(lastDay, 'JST', 'yyyy-MM-dd (E)')}`);
  });
 
}

初回実行時には、Google アカウントを選択し、権限を付与する必要があります。

正常終了すると、以下のようなログが出力されます。

第1営業日のイベント登録処理

実装

新しいファイル create_event.gs を作成し、以下の処理を実装します。

/**
 * イベントを作成するカレンダーを指定します。
 * デフォルトカレンダー以外を使いたい場合は、'xxxx@group.calendar.google.com' のようなカレンダーIDに書き換えてください。
 */
const TARGET_CALENDAR_ID = 'primary'; // 'primary' はデフォルトカレンダーを意味します
 
/**
 * カレンダーに作成するイベントの内容をここで定義します。
 */
const EVENT_TITLE = '【月次】勤怠締め作業を上長申請';
const EVENT_DESCRIPTION = '前月分の勤怠の締め作業をシステムを使用して上長に申請する。';
 
/**
 * イベントを作成する開始年月日と終了年月日を定義します。
 */
const START_DATE = new Date(2025, 6, 25, 0, 0, 0, 0); // 2025/07/25
const END_DATE = new Date(2026, 1, 28, 0, 0, 0, 0); // 2026/02/28
 
/**
 * イベントを作成する時の開始時間と終了時間を定義します。
 */
const START_TIME = new Date(0, 0, 0, 9, 0, 0, 0); // 9:00
const END_TIME = new Date(0, 0, 0, 9, 30, 0, 0); // 9:30
 
/**
 * 指定された期間内の営業日の定例イベントをGoogleカレンダーに作成します。
 * @param {function} getBusinessDay - 営業日を計算する関数(第1引数:年、第2引数:月)
 */
const createBusinessDayEvents_ = function(getBusinessDay) {
  
  // 1. 設定のチェック
  if (START_DATE > END_DATE) {
    console.error('エラー: 開始年月日は終了年月日以前の日付にしてください。');
    return;
  }
 
  if (START_TIME > END_TIME) {
    console.error('エラー: 開始時間は終了時間以前の時間にしてください。');
    return;
  }
 
  if(typeof getBusinessDay != 'function') {
    console.error('エラー: 引数には営業日を計算する関数を設定してください。');
    return;
  }
 
  const calendar = CalendarApp.getCalendarById(TARGET_CALENDAR_ID);
  if (!calendar) {
    console.error(`エラー: 指定されたカレンダーID (${TARGET_CALENDAR_ID}) が見つかりません。`);
    return;
  }
 
  console.log('処理を開始します...');
  console.log(`対象期間: ${Utilities.formatDate(START_DATE, 'JST', 'yyyy-MM-dd')} ~ ${Utilities.formatDate(END_DATE, 'JST', 'yyyy-MM-dd')}`);
 
  // 2. 指定期間内の営業日を計算
  // 月ごとにループ
  const recurrence = CalendarApp.newRecurrence();
  const recurrenceDaysList = [];
  let currentDate = new Date(START_DATE);
  while (currentDate <= END_DATE) {
    const year = currentDate.getFullYear();
    const month = currentDate.getMonth() + 1; // 月は1始まりに補正
 
    // 営業日を計算
    const businessDay = getBusinessDay(year, month);
    const formattedDate = Utilities.formatDate(businessDay, 'JST', 'yyyy-MM-dd');
 
    // 計算した営業日が指定期間内にある場合のみタスクを作成
    if (businessDay >= START_DATE && businessDay <= END_DATE) {
      recurrence.addDate(businessDay);
      recurrenceDaysList.push(businessDay);
      console.log(`対象をリストに追加しました: ${formattedDate}`);
    } else {
      console.log(`指定期間外のためスキップしました: ${formattedDate}`);
    }
 
    // 次の月へ
    currentDate.setMonth(currentDate.getMonth() + 1);
    currentDate.setDate(1); // 日を1日にリセット
  }
 
  // 3. 定期的な予定の作成
  if (recurrenceDaysList.length > 0) {
    const initialDay =  recurrenceDaysList[0];
    const startDate = new Date(initialDay);
    startDate.setHours(START_TIME.getHours());
    startDate.setMinutes(START_TIME.getMinutes());
    const endDate = new Date(initialDay);
    endDate.setHours(END_TIME.getHours());
    endDate.setMinutes(END_TIME.getMinutes());
    calendar.createEventSeries(
      EVENT_TITLE,
      startDate,
      endDate,
      recurrence,
      { description: EVENT_DESCRIPTION },
    );
    console.log('定期的な予定を作成しました。');
  } else {
    console.log('指定期間に対象の日付がないため、定期的な予定を作成していません。');
  }
 
};
 
/**
 * 指定された期間内の各月の第1営業日の定例イベントをGoogleカレンダーに作成します。
 */
function createFirstBusinessDayEvents() {
  createBusinessDayEvents_(getFirstBusinessDay);
}

createBusinessDayEvents_ ではまず、 CalendarApp クラスを使用し、自身の Google カレンダーの情報を取得します。

次に、 RecurrenceRule クラスを使用して、定期的な予定のルールを作成していきます。引数の営業日計算用の関数を使用して営業日の計算を行い、 addDate メソッドを使用して定期的な予定に設定する営業日を追加します。

最後に、 Calendar クラスの createEventSeries メソッドを使用して定期的な予定を登録します。

動作確認

createFirstBusinessDayEvents 関数を実行すると、カレンダーに定期的な予定が登録されます。以下のように、 Google カレンダーに指定した期間の予定が登録されていることが確認できます。

Google カレンダー上では登録された予定には繰り返し設定がないと表示されていますが、
スケジュールの削除を実行すると、定期的な予定の削除のダイアログが表示され、定期的な予定となっていることが確認できます。

また、2025年11月や2026年1月を確認すると、土日祝日、会社休日の設定が考慮された、第1営業日が登録されていることを確認できます。

最終営業日のイベント登録処理

最終営業日にタスクを作成したい場合は、create_event.gs に以下のコードを実装します。

createBusinessDayEvents_ 関数に最終営業日計算用の関数を引数として渡すことで実装できます。createLastBusinessDayEvents 関数を実行すると、最終営業日に定期的な予定を登録できます。

/**
 * 指定された期間内の各月の最終営業日の定例イベントをGoogleカレンダーに作成します。
 */
function createLastBusinessDayEvents() {
  createBusinessDayEvents_(getLastBusinessDay);
}

高宮 怜(記事一覧)

クラウドソリューション部クラウドエクスプローラ課

2025年6月より、G-genにジョイン。前職は四国のSIerで電力、製造業系のお客様に対して、PM/APエンジニアとして、要件定義から運用保守まで全工程を担当。現在はGoogle Cloudを学びながら、フルスタックエンジニアを目指してクラウドエンジニアとしてのスキルを習得中。