誕生日カレンダーの登録ミスを作成前に発見|GASで日付と重複をdry-run監査

誕生日カレンダーの登録ミスを作成前に発見|GASで日付と重複をdry-run監査

ツールおじ|GoogleアカウントをAIエージェント化

ツールおじ|GoogleアカウントをAIエージェント化

誕生日の予定をまとめて登録しようとして、日付の入力ミスや同じ人の二重登録に気づかないまま実行するのは不安です。この記事では、カレンダーへ予定を1件も作成せず、入力不備・リスト内の重複・既存予定との重なりを先に確認する無料範囲のGASを紹介します。

先に知っておきたいこと

  • このコードは誕生日イベントを作成・更新・削除しません。
  • 対象年の予定候補を組み立て、監査結果シートへ判定を書き出します。
  • 既存カレンダーは読み取りだけです。共有範囲と実行アカウントを先に確認してください。
  • 誕生日は個人情報に当たる場合があります。実名ではなくテスト用データから試してください。

実行前の準備

スプレッドシートに「誕生日リスト」シートを作り、1行目を見出しにします。

  • A列: 名前
  • B列: 誕生日
  • 例: テスト太郎 / 1990-04-15

コード内のカレンダーIDは、検証用カレンダーのIDへ置き換えます。最初は2〜3件の架空データだけで試してください。

コピーして使える最小コード

const BIRTHDAY_AUDIT_CONFIG = {
  DRY_RUN: true,
  SOURCE_SHEET: '誕生日リスト',
  RESULT_SHEET: '誕生日監査結果',
  CALENDAR_ID: '検証用カレンダーIDを入力',
  TARGET_YEAR: new Date().getFullYear(),
  MAX_ROWS: 200
};

function auditBirthdayCalendarPlanFree() {
  const config = BIRTHDAY_AUDIT_CONFIG;
  if (config.DRY_RUN !== true) {
    throw new Error('安全のためDRY_RUNはtrueのまま実行してください。');
  }
  if (!config.CALENDAR_ID || config.CALENDAR_ID.includes('入力')) {
    throw new Error('検証用カレンダーIDを設定してください。');
  }

  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const source = spreadsheet.getSheetByName(config.SOURCE_SHEET);
  if (!source) {
    throw new Error('誕生日リストシートが見つかりません。');
  }

  const lastRow = source.getLastRow();
  if (lastRow < 2) {
    throw new Error('監査するデータがありません。');
  }
  const rowCount = Math.min(lastRow - 1, config.MAX_ROWS);
  if (lastRow - 1 > config.MAX_ROWS) {
    throw new Error('件数が上限を超えています。MAX_ROWS以下に分けてください。');
  }

  const calendar = CalendarApp.getCalendarById(config.CALENDAR_ID);
  if (!calendar) {
    throw new Error('指定したカレンダーを読み取れません。IDと権限を確認してください。');
  }

  const values = source.getRange(2, 1, rowCount, 2).getValues();
  const seen = new Set();
  const results = [['行', '予定日', '判定', '確認内容']];

  values.forEach((row, index) => {
    const sheetRow = index + 2;
    const name = String(row[0] || '').trim();
    const birthday = normalizeBirthdayDate_(row[1], config.TARGET_YEAR);

    if (!name || !birthday) {
      results.push([sheetRow, '', 'HOLD', '名前または誕生日を確認']);
      return;
    }

    const dateKey = Utilities.formatDate(
      birthday,
      Session.getScriptTimeZone(),
      'yyyy-MM-dd'
    );
    const duplicateKey = name.toLowerCase() + '|' + dateKey;
    if (seen.has(duplicateKey)) {
      results.push([sheetRow, dateKey, 'HOLD', 'リスト内に同じ名前と日付あり']);
      return;
    }
    seen.add(duplicateKey);

    const expectedTitle = name + 'さんの誕生日';
    const existing = calendar.getEventsForDay(birthday).some((event) => {
      return event.getTitle().trim() === expectedTitle;
    });
    results.push([
      sheetRow,
      dateKey,
      existing ? 'HOLD' : 'READY',
      existing ? '同じタイトルの既存予定あり' : '作成候補として確認可能'
    ]);
  });

  let resultSheet = spreadsheet.getSheetByName(config.RESULT_SHEET);
  if (!resultSheet) {
    resultSheet = spreadsheet.insertSheet(config.RESULT_SHEET);
  }
  resultSheet.clearContents();
  resultSheet.getRange(1, 1, results.length, results[0].length).setValues(results);

  const summary = results.slice(1).reduce((counts, row) => {
    counts[row[2]] = (counts[row[2]] || 0) + 1;
    return counts;
  }, {});
  console.log(JSON.stringify({
    dryRun: true,
    checkedRows: rowCount,
    ready: summary.READY || 0,
    hold: summary.HOLD || 0
  }));
}

function normalizeBirthdayDate_(value, targetYear) {
  const sourceDate = value instanceof Date ? new Date(value) : new Date(String(value));
  if (Number.isNaN(sourceDate.getTime())) return null;

  const month = sourceDate.getMonth();
  const day = sourceDate.getDate();
  const normalized = new Date(targetYear, month, day);
  if (normalized.getMonth() !== month || normalized.getDate() !== day) return null;
  normalized.setHours(0, 0, 0, 0);
  return normalized;
}

実行手順

1. 元ファイルを複製し、架空の名前と誕生日を2〜3行だけ入力します。

2. Apps Scriptへコードを貼り、検証用カレンダーIDを設定します。

3. auditBirthdayCalendarPlanFreeを実行します。

4. 「誕生日監査結果」シートでREADYとHOLDの件数を確認します。

5. HOLDが1件でもあれば、元の入力または既存予定を手動で確認します。

この時点では予定は作成されません。監査結果を確認した後も、最初の本登録は少数件で行い、カレンダー画面から結果を確認してください。

判定の見方

  • READY: 入力形式、リスト内重複、同日同名の既存予定に問題が見つからなかった候補
  • HOLD: 空欄、不正な日付、リスト内重複、または既存予定との一致があった候補

READYは登録成功の保証ではありません。タイムゾーン、うるう日、カレンダー権限、Apps Scriptの実行上限は別途確認が必要です。

停止条件

  • 意図しないカレンダーIDが設定されている
  • 共有範囲や実行アカウントを確認できない
  • 実データに不要な閲覧者がいる
  • HOLDが1件以上残っている
  • 対象件数がMAX_ROWSを超えている

制限と安全上の注意

この例は同じ日付・同じタイトルの既存予定だけを照合します。表記ゆれ、別カレンダー、別タイトルの重複までは判断しません。また、監査結果シートには行番号と予定日が残るため、元シートと同じ共有制限で管理してください。

ログには名前や予定タイトルを出さず、件数だけを記録します。必要以上の個人情報を外部サービスへ送る処理も含みません。

まとめ

誕生日リマインダーの自動化では、予定作成そのものより、入力不備と重複を作成前に止められるかが重要です。まずdry-runで候補を監査し、HOLDを解消してから少量ずつ確認してください。

期限や朝の確認項目もまとめて整えたい方へ

Morning ToDo Mail Proを見る

GasおじのTips商品一覧を見る

GasおじのTips商品一覧を見る

※学習・検証用の例です。実データへ適用する前にコピー環境で確認してください。Google公式・Google公認の商品ではありません。


あなたも記事の投稿・販売を
始めてみませんか?

Tipsなら簡単に記事を販売できます!
登録無料で始められます!

Tipsなら、無料ですぐに記事の販売をはじめることができます Tipsの詳細はこちら
 

この記事のライター

ツールおじ|GoogleアカウントをAIエージェント化

おう、Gasおじだ。 AIツールとSaaSに散々突っ込んで遠回りした結果、最後に強かったのは「今あるGoogleアカウント」だった。 今ではAIエージェントであらゆるタスクを自動化して無双してるぞ。 GAS、スプレッドシート、フォーム、Gmail、AIエージェントを組み合わせれば、個人でも小さな会社でも、手作業だらけの業務はかなり自動化できる。 ここでは机上のAI論じゃなく、現場で動くテンプレ、コード、手順だけを置いていくぞ。 高いツールを増やす前に、まずお前さんのGoogleアカウントを仕事するエージェントに変えようじゃないか。

このライターが書いた他の記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
  • あとで見るURLを二重登録しない|GASで読書・動画ウォッチリストをdry-run監査

  • Google Driveのフォルダを一括作成する前に確認|GASのdry-run付き安全テンプレート

関連のおすすめ記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化

  • 【Python】 Con-Que 【データベース接続】

    ¥3,000
    1 %獲得
    (30 円相当)
    GoGorilla

    GoGorilla

  • USBメモリで持ち運べるLinux Mint 22.1と永続的な書き込み領域の作成法【Rufus だけで作成する方法を初心者向けに徹底解説】

    ¥500
    1 %獲得
    (5 円相当)
    SmartSenior

    SmartSenior