Claudeにコードを書いてもらう①【Googleカレンダーの予定を別日に移動するGASスクリプト】

ITハック
スポンサーリンク

今回から連載形式でチャット型生成AIツールである「Claude(クロード)」にプログラムのコードを書いてもらう流れを備忘録的にまとめていく。

第1回目となる今回は、以前「指定された期間のGoogleカレンダー上の予定を別日に移動させるGASスクリプト」の記事で紹介したスクリプトについて、実際に「Claudeにプロンプトを与えてコードを書いてもらう」→「出力されたコードの動作を確認して問題があれば修正を依頼する」といった流れを紹介する。

Claudeについての詳細は以下の公式サイトを参照。ざっくりと言ってしまえば「Chat-GPT」や「Gemini」など、皆さんもおなじみの生成AIツールと同じようなものだ。

また、Claudeは公式のドキュメントが充実しており、各種モデルの詳細や料金体系、開発者用のAPIなどの詳細を知りたい方は以下の公式ドキュメントを参照。

スポンサーリンク

使用するモデル

今回は2025年2月に公開されたばかりの「Claude 3.7 Sonnet(ソネット)」というモデルを使用する。

Sonnetは現時点において無料で使える唯一のモデルであり、高速なレスポンスが売りの「Haiku(ハイク)」や高度な推論・分析を得意とする「Opus(オーパス)」の他2つのモデルについては有料プランでのみ使用可能。

以下のようにチャットの入力欄から使用するモデルを選択できるが、Sonnet以外は「Pro」のラベルが付いており有料プランへの加入を求められる。

ちなみに各プランの料金体系は以下の通り。

他生成AIツールの有料版である「Chat-GPT Plus」や「Gemini Advanced」が月額3,000円前後なのを考えると、それらに相当する「Claude Pro」は2,500円(1$ = 145円換算)と若干だがお安めの印象だ。

実装してもらうコードの概要

今回のGASスクリプトのコードは以下の通り。詳細については「【GAS】Googleカレンダーの予定を別日に移動するスクリプト」の記事で解説しているのでそちらを参照。

/**
 * Google Calendar上の予定を指定期間から別の日付に移動するスクリプト
 * - 予定を期間(複数日)で指定して移動
 * - タイトルに特定のキーワードを含む予定は除外
 * - 移動対象期間、移動先日付、除外キーワードはスプレッドシートから取得
 */

// メイン関数
function moveCalendarEvents() {

    try {
        // スプレッドシートから設定値を取得
        const settings = getSettings();

        // 対象期間の予定を取得
        const events = fetchEvents(settings.startDate, settings.endDate, settings.excludeKeywords);

        // 予定を移動
        const movedCount = moveEvents(events, settings.daysDifference);

        // 結果をログ出力
        console.log(`移動完了: ${movedCount}件の予定を移動しました。`);

    } catch (error) {
        console.log(`エラーが発生しました: ${error.message}`);
    }
}

/**
 * スプレッドシートから設定値を取得する
 * @return {Object} 設定値のオブジェクト
 */
function getSettings() {

    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("設定");

    // 各設定値の取得
    const startDateValue = sheet.getRange("B2").getValue(); // 移動対象の開始日
    const endDateValue = sheet.getRange("B3").getValue(); // 移動対象の終了日
    const targetDateValue = sheet.getRange("B4").getValue(); // 移動先の日付(最初の日)
    const excludeKeywordsValue = sheet.getRange("B5").getValue(); // 除外キーワード(カンマ区切りで複数指定可能)

    // 日付のバリデーション
    if (
        !(startDateValue instanceof Date) ||
        !(endDateValue instanceof Date) ||
        !(targetDateValue instanceof Date)
    ) {
        throw new Error("日付の形式が正しくありません");
    }

    // 終了日を1日後の0時に設定
    const adjustedEndDate = new Date(endDateValue);
    adjustedEndDate.setDate(endDateValue.getDate() + 1);

    // 除外キーワードの処理(カンマ区切りでキーワードを指定可能に)
    const excludeKeywords = excludeKeywordsValue ? excludeKeywordsValue.split(",").map((keyword) => keyword.trim()) : [];

    // 移動日数の計算(対象期間の開始日と移動先日付の差)
    const daysDifference = Math.floor((targetDateValue - startDateValue) / (1000 * 60 * 60 * 24));

    return {
        startDate: startDateValue,
        endDate: adjustedEndDate,
        daysDifference: daysDifference,
        excludeKeywords: excludeKeywords,
    };
}

/**
 * 指定期間内の予定を取得する(除外キーワードを含む予定は除外)
 * @param {Date} startDate - 開始日
 * @param {Date} endDate - 終了日
 * @param {Array} excludeKeywords - 除外キーワードのリスト
 * @return {Array} 取得した予定の配列
 */
function fetchEvents(startDate, endDate, excludeKeywords) {

    // デフォルトカレンダーを取得
    const calendar = CalendarApp.getDefaultCalendar();

    // 指定期間の予定を取得
    const allEvents = calendar.getEvents(startDate, endDate);

    // 除外キーワードを含まない予定だけをフィルタリング
    const filteredEvents = allEvents.filter((event) => {
        const title = event.getTitle();
        return !excludeKeywords.some((keyword) => title.includes(keyword));
    });

    return filteredEvents;
}

/**
 * 予定を指定した日数分移動する
 * @param {Array} events - 移動する予定の配列
 * @param {number} daysDifference - 移動する日数
 * @return {Object} 移動結果の統計
 */
function moveEvents(events, daysDifference) {

    let movedCount = 0;

    // 各予定を移動
    events.forEach((event) => {
        // 開始時刻と終了時刻を取得
        const startTime = event.getStartTime();
        const endTime = event.getEndTime();

        // 新しい日時を計算(時刻はそのまま、日付だけ移動)
        const newStartTime = new Date(startTime);
        newStartTime.setDate(startTime.getDate() + daysDifference);

        const newEndTime = new Date(endTime);
        newEndTime.setDate(endTime.getDate() + daysDifference);

        // 予定を移動
        event.setTime(newStartTime, newEndTime);
        movedCount++;
    });

    return movedCount;
}

Claudeに与えるプロンプト

ここからは実際にClaudeに対して与えるプロンプト(命令)とClaudeからの回答を紹介する。

以降のプロンプトに関してはそのままコピペで使えるようにしている。

以下に記載するのは2025年5月時点の情報です。

Claudeのモデルやバーション、時期によっては同じプロンプトを与えたとしても同じ回答を得られない可能性がありますのでご了承ください。

プロンプト①:機能要件を伝えて実装してもらう

以下の実装方針、要件を満たす「指定された期間のGoogleカレンダー上の予定を取得して別日に移動させるコード」を書いてください。

◆ 実装方針
・GAS(JavaScript)で書く
・コードはできるだけモダンJavaScript(ECMAScriptの最新)の構文を用いて書くが、最低限の可読性、拡張性、保守性は担保し、必ずしも「効率のいい書き方」に捉われないようにする
・処理の固まりがある程度の規模になった際は適宜関数化する
・コメントは多すぎず、処理の固まりごとに適切に記載し、コードから読み取れる処理が明らかである場合は省略する
・バリデーションチェック、エラー処理、結果ログの出力等はコードが煩雑になるためひとまずは必要最低限で実装する(必要があれば後ほど追加で依頼)

◆ 要件
・予定を期間(複数日)で指定して移動する
・タイトルに特定のキーワードを含んだ予定は移動対象から除外する
・「移動対象の期間」「移動先の日付」「除外するキーワード」はスプレッドシート内の特定のセルから取得できるようにする

まず初めに、ざっくりと「実装方針」「機能要件」の2つを伝えて必要な機能を満たすスクリプトを実装してもらう。

ポイントとしては、GASスクリプトのコードを書いてもらう際のプロンプトのフォーマットを決めておくこと。

ここでは「モダンJavaScriptで書く」「適切に関数ごとに処理を整理する」「(個人で利用するスクリプトなので)エラーチェックなどの処理は最低限で」などの実装方針を伝えている。

この辺りは人それぞれ好みや実装するスクリプトによって方針が異なるかと思うが、「自分なりのルール・型」をある程度決めて固定しておくとAIからの回答の品質も安定し、またプロンプトの再利用もしやすくなる。

機能要件については実現したい処理をそのまま列挙して伝える感じだが、最初からすべての要件を完璧に洗い出す必要はなく、最初に出力されたコードをもとに機能の改修や細かい条件ごとの判定を追加で依頼することも可能なので、まずは気軽に「こういった処理を行うスクリプトが欲しいな」くらいのレベルで書いてみるのがよい。

続いて、上記のプロンプトに対するClaudeからの回答がこちら。

Google Apps Script (GAS) で指定された期間のGoogle Calendar上の予定を別日に移動させるコードを作成します。要件に従って、モダンなJavaScriptで書きつつ、読みやすさと保守性を確保します。

~~~ 出力されたコード ~~~

このコードは以下の機能を実装しています:

  1. メイン関数: moveCalendarEvents() - 全体の処理の流れを制御します
  2. 設定取得: getSettings() - スプレッドシートから設定値を取得します
  3. 予定取得: fetchEvents() - 指定期間の予定を取得し、除外キーワードでフィルタリングします
  4. 予定移動: moveEvents() - 予定を指定した日数分移動します
  5. 結果記録: recordResult() - 実行結果をスプレッドシートに記録します

使用方法

  1. スプレッドシートに「設定」シートを作成し、以下のセルを設定します:
    ・B2: 移動対象の開始日
    ・B3: 移動対象の終了日
    ・B4: 移動先の日付(最初の日)
    ・B5: 除外キーワード(カンマ区切りで複数指定可能)
  2. 「実行結果」シートを作成します(オプション)
  3. スクリプトエディタでコードを実行し、必要な権限を許可します

このコードは必要に応じて拡張できる基本的な実装になっています。エラー処理や詳細なバリデーションが必要な場合は、さらに機能を追加できます。

実際に出力されたコードは以下の通り。ものの2~3分でそのまま使えそうなコードを実装してくれた。

/**
 * Google Calendar上の予定を指定期間から別の日付に移動するスクリプト
 * - 予定を期間(複数日)で指定して移動
 * - タイトルに特定のキーワードを含む予定は除外
 * - 移動対象期間、移動先日付、除外キーワードはスプレッドシートから取得
 */

// メイン関数
function moveCalendarEvents() {
  try {
    // スプレッドシートから設定値を取得
    const settings = getSettings();
    
    // 対象期間の予定を取得
    const events = fetchEvents(settings.startDate, settings.endDate, settings.excludeKeywords);
    
    // 予定を移動
    const result = moveEvents(events, settings.daysDifference);
    
    // 結果をログ出力
    Logger.log(`移動完了: ${result.moved}件移動、${result.excluded}件除外`);
    
    // スプレッドシートに結果を記録(オプション)
    recordResult(result);
  } catch (error) {
    Logger.log(`エラーが発生しました: ${error.message}`);
  }
}

/**
 * スプレッドシートから設定値を取得する
 * @return {Object} 設定値のオブジェクト
 */
function getSettings() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('設定');
  
  // 各設定値の取得
  const startDateValue = sheet.getRange('B2').getValue();
  const endDateValue = sheet.getRange('B3').getValue();
  const targetDateValue = sheet.getRange('B4').getValue();
  const excludeKeywordsValue = sheet.getRange('B5').getValue();
  
  // 日付のバリデーション
  if (!(startDateValue instanceof Date) || !(endDateValue instanceof Date) || !(targetDateValue instanceof Date)) {
    throw new Error('日付の形式が正しくありません');
  }
  
  // 除外キーワードの処理(カンマ区切りでキーワードを指定可能に)
  const excludeKeywords = excludeKeywordsValue ? excludeKeywordsValue.split(',').map(keyword => keyword.trim()) : [];
  
  // 移動日数の計算(対象期間の開始日と移動先日付の差)
  const daysDifference = Math.floor((targetDateValue - startDateValue) / (1000 * 60 * 60 * 24));
  
  return {
    startDate: startDateValue,
    endDate: endDateValue,
    daysDifference: daysDifference,
    excludeKeywords: excludeKeywords
  };
}

/**
 * 指定期間内の予定を取得する(除外キーワードを含む予定は除外)
 * @param {Date} startDate - 開始日
 * @param {Date} endDate - 終了日
 * @param {Array} excludeKeywords - 除外キーワードのリスト
 * @return {Array} 取得した予定の配列
 */
function fetchEvents(startDate, endDate, excludeKeywords) {
  // デフォルトカレンダーを取得
  const calendar = CalendarApp.getDefaultCalendar();
  
  // 指定期間の予定を取得
  const allEvents = calendar.getEvents(startDate, endDate);
  
  // 除外キーワードを含まない予定だけをフィルタリング
  const filteredEvents = allEvents.filter(event => {
    const title = event.getTitle();
    return !excludeKeywords.some(keyword => title.includes(keyword));
  });
  
  return filteredEvents;
}

/**
 * 予定を指定した日数分移動する
 * @param {Array} events - 移動する予定の配列
 * @param {number} daysDifference - 移動する日数
 * @return {Object} 移動結果の統計
 */
function moveEvents(events, daysDifference) {
  let movedCount = 0;
  const excludedCount = 0; // 既に fetchEvents でフィルタリング済み
  
  // 各予定を移動
  events.forEach(event => {
    // 開始時刻と終了時刻を取得
    const startTime = event.getStartTime();
    const endTime = event.getEndTime();
    
    // 新しい日時を計算(時刻はそのまま、日付だけ移動)
    const newStartTime = new Date(startTime);
    newStartTime.setDate(startTime.getDate() + daysDifference);
    
    const newEndTime = new Date(endTime);
    newEndTime.setDate(endTime.getDate() + daysDifference);
    
    // 予定を移動
    event.setTime(newStartTime, newEndTime);
    movedCount++;
  });
  
  return {
    moved: movedCount,
    excluded: excludedCount,
    total: events.length
  };
}

/**
 * 結果をスプレッドシートに記録する
 * @param {Object} result - 移動結果の統計
 */
function recordResult(result) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('実行結果');
  
  // 現在の日時を取得
  const now = new Date();
  
  // 結果を記録
  const newRow = [
    now,
    result.moved,
    result.excluded,
    result.total
  ];
  
  // 最終行の次に新しい行を追加
  sheet.appendRow(newRow);
}

スクリプトを動かす準備をする

実際にスクリプトを動かしてみて、想定通りの処理になっているかを検証する。

まずGoogleドライブ上に任意のスプシを新規作成し、上部メニューの「拡張機能」>「Apps Script」からGASのコードエディタを開く。

エディタにはデフォルトで空のfunction()が記載されているが、それを上書きする形でClaudeが出力したコードをそのままコピペして貼り付け、保存する。

これでスクリプト側の準備は完了。

続いてスプシ上での前準備を行う。

コード内では「設定」「実行結果」という2つのシートを取得して処理をしているようなので、シートを追加して名前を設定する。

また、機能要件で記載した「移動対象の期間」「移動先の日付」「除外するキーワード」の値については、それぞれ38~41行目の処理で取得しているので、コードに記載のある通りB2~B5セルに必要な情報を入力する。

ここでは「移動対象の期間:2025/6/2~6/3の2日間」「移動先の日付:2025/6/5~6/6」「除外するキーワード:睡眠、身支度(カンマ区切りで指定)」とする。

これでスプシの準備も完了。

最後にGoogleカレンダー上に予定を作成する。

今回は以下のような形で、移動対象の期間(6/2~6/3)に移動対象となる予定(出勤、書類整理、資料作成)と移動対象から除外される予定(睡眠、朝の身支度)を作ってみた。

スクリプトが正しく動けば、「睡眠」「朝の身支度」の予定はそのままで、「出勤」は6/2→6/5の同じ時間帯へ、「書類整理」「資料作成」は6/3→6/6の同じ時間帯へ、計3件の予定がそれぞれの日時へ移動されるはずだ。

実際に動かしてみる

それでは実際にスクリプトを実行してみる。

実行対象の関数が「moveCalendarEvents(メインの関数)」になっていることを確認し、実行ボタン(またはCtrl +r)で実行する。

エラーなく正常に処理が完了すると、画面下に実行ログが表示される。

ここで実行結果は「移動完了:1件移動、0件除外」となっており、先ほど想定していた結果と何やら違いそうだ。

実際に予定が移動されているかGoogleカレンダーも見てみる。

ログで出力されていた通り、「出勤」の1件の予定のみが移動されており、移動されて欲しい「書類整理」「資料作成」などの予定が移動されていなかった。

実行結果シートも併せて確認してみる。

ログと同じように移動された予定の数と実行日時が入力されていそうだが、除外対象の予定の件数が間違っているうえに、正直これだけだと何が何だか分からない。

そもそもログ自体は実行ログの方にも出力されているので、別途シートに入力する必要はなさそうに感じる。

以上を踏まえて、諸々コードを修正していく。

プロンプト②:不具合の修正をしてもらう

まずは先ほどの「対象の予定が移動されていない問題」を解決していく。

先ほどの結果をもう一度見てみると、移動対象の開始日の「出勤」は正常に移動され、逆に「睡眠」や「朝の身支度」は設定したキーワードに引っかかり移動対象から正しく除外されていたので、除外キーワード周りの処理は問題なさそうだ。

逆に移動対象の終了日の予定はすべて移動されていなかったため、日付の取得周りが怪しいかもしれないと仮定して検証を進めていく。

確認のために各情報をスプシから取得したあとに、「console.log()」で移動対象の開始日/終了日の実際の値を見てみる。

console.log(`開始日:${startDateValue}`);
console.log(`終了日:${endDateValue}`);

実行結果は以下の通り。ここで終了日の値に注目して欲しいのだが、値が「Tue Jun 03 2025 00:00:00」となっており、先ほど予想した通り終了日が「2025年6月3日の0時 = 6月3日の開始時間」となっていて終了日の予定が取得できていなさそうなことが分かる。

この事象に対して、Claudeにコードの修正をしてもらうよう追加のプロンプトを与える。

上記のコードだとB3のセルから取得した最終日(endDateValue)の値が「Tue Jun 03 2025 00:00:00 GMT+0900 (日本標準時)」となっており、結果的に後続の処理で最終日の予定が取得の対象とならないようです。

こちらについて最終日の予定が取得できるように修正し、修正した部分のみ出力をお願いします。

これに対してClaudeからの回答がこちら。やはり終了日の予定が取得されておらず、そこに対して修正を加える必要があることが分かる。

ここでは出力された修正例をそのまま使うことにする。

修正(追加)されたコードだけ抜き出すと以下の部分。単純な処理だが、スプシから取得した終了日に+1日することで終了日が1日延長され(今回の例では「2025年6月4日 0時」)、結果的に終了日の予定も取得できるようになる。

// 終了日を1日後の0時に設定
const adjustedEndDate = new Date(endDateValue);
adjustedEndDate.setDate(endDateValue.getDate() + 1);

ここで、Claudeのコード例ではfetchEvents関数に渡したあとで終了日+1日としているが、各関数の内容的に処理の意図が若干分かりづらくなるので、getSettings関数の中でスプシ上から値を取得した直後に処理を行うようにするとよさそうだ。

以上を踏まえて修正したgetSettings関数は以下の通り。returnでの終了日の戻り値は「endDateValue」ではなく+1日後の「adjustedEndDate」であることに注意しよう。

/**
 * スプレッドシートから設定値を取得する
 * @return {Object} 設定値のオブジェクト
 */
function getSettings() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('設定');
  
  // 各設定値の取得
  const startDateValue = sheet.getRange('B2').getValue();
  const endDateValue = sheet.getRange('B3').getValue();
  const targetDateValue = sheet.getRange('B4').getValue();
  const excludeKeywordsValue = sheet.getRange('B5').getValue();

  // console.log(`開始日:${startDateValue}`);
  // console.log(`終了日:${endDateValue}`);
  
  // 日付のバリデーション
  if (!(startDateValue instanceof Date) || !(endDateValue instanceof Date) || !(targetDateValue instanceof Date)) {
    throw new Error('日付の形式が正しくありません');
  }

  // 終了日を1日後の0時に設定
  const adjustedEndDate = new Date(endDateValue);
  adjustedEndDate.setDate(endDateValue.getDate() + 1);
  
  // 除外キーワードの処理(カンマ区切りでキーワードを指定可能に)
  const excludeKeywords = excludeKeywordsValue ? excludeKeywordsValue.split(',').map(keyword => keyword.trim()) : [];
  
  // 移動日数の計算(対象期間の開始日と移動先日付の差)
  const daysDifference = Math.floor((targetDateValue - startDateValue) / (1000 * 60 * 60 * 24));
  
  return {
    startDate: startDateValue,
    endDate: endDateValue,
    daysDifference: daysDifference,
    excludeKeywords: excludeKeywords
  };
}

2つめの「『実行結果』シート上への結果出力は不要」の件だが、こちらは単純に処理を削ればよいだけなのでClaudeに依頼するまでもなく、該当部分のコードを削除して少し手直しを加えることにする。

該当する処理は以下のrecordResult関数なので、以下のコードはそのまま削除してしまう。併せてメインのmoveCalendarEvents関数の中でrecordResult関数を呼びだしている部分も削除しておこう。

/**
 * 以下のコードは不要なので削除
 */

/**
 * 結果をスプレッドシートに記録する
 * @param {Object} result - 移動結果の統計
 */
function recordResult(result) {
    const sheet =
        SpreadsheetApp.getActiveSpreadsheet().getSheetByName("実行結果");

    // 現在の日時を取得
    const now = new Date();

    // 結果を記録
    const newRow = [now, result.moved, result.excluded, result.total];

    // 最終行の次に新しい行を追加
    sheet.appendRow(newRow);
}

また、recordResult関数を削ったことで、moveEvents関数の戻り値で「excludedCount」「events.length」辺りの値も不要となるので、「movedCount」だけを返すように変更する。

/**
 * moveEvents関数の戻り値を以下に修正
 */

return movedCount;

最後にメインのmoveCalendarEvents関数の中でconsole.log()での結果出力に関しても移動件数(movedCount)のみを出力するようにする。

/**
 * moveCalendarEvents関数のログ出力を以下に修正
 */

console.log(`移動完了: ${movedCount}件の予定を移動しました。`);

これで諸々の修正は完了。「実行結果」シートも不要になったので削除しておこう。

再度動かしてみる

修正後のスクリプトの実行結果は以下の通り。Googleカレンダー上の予定もログの出力結果も、想定通り3件の予定が移動されていることが分かる。

これでコードの実装は完了だ。

皆さん自身の環境でも移動対象の期間やGoogleカレンダーの予定を色々変えてみて、動作を確認してみるとよいだろう。

まとめ

今回はチャット型生成AIツールのClaudeにプログラムのコードを書いてもらう流れについて、「指定された期間のGoogleカレンダー上の予定を別日に移動させるGASスクリプト」を対象に「Claudeにプロンプトを与えてコードを書いてもらう」→「出力されたコードの動作を確認して問題があれば修正を依頼する」の方法を紹介した。

2025年5月現在、AIのチャットツールやエージェントは非常に速い速度で進化しており、自分自身も仕事やプライベートでAIツールを用いる機会が多くなってきたが、そこで得た知見やアイディアを今後も不定期で共有していきたいと思う。

今までClaudeに書いてもらったソースコード、スクリプトの一覧はこちら。

タイトルとURLをコピーしました