【GAS】時間割をカレンダーに流し込む

Googleスプレッドシート上に作成した時間割をGoogleカレンダーに流し込むGASです。

2020年に作成したプロトタイプを安藤先生がUIとかも含めて改変、ご自身のYouTubeチャンネル「[GIGAch](https://www.youtube.com/@gigach)」で紹介して下さって以降、ともかつさんの「[ともかつのノート](https://note.com/tomo_saka/n/nf9f4c8cc0547)」上でさらにカスタマイズし、使いやすくして下さいました。

そこで、2020年当初の設計思想を確認し、それを元に機能を制限してコード全体を整理し直したものを公開しておきます。細かい使い方は、ベースとなっている「[ともかつのノート](https://note.com/tomo_saka/n/nf9f4c8cc0547)」を参照して下さい。

## 設計思想

- 時間割は基本的に年間を通して変わらないため、一括でカレンダーに流し込みたい。
- 行事や祝祭日がイレギュラーで入るため、期間を区切って流し込む必要がある。
- 各時限の開始、終了時間は可変とすることで汎用性を持たせる。

これを出発点に種々の目的に応じた活用の場が想定され、それに応じた改良が加えられていったのが面白い。起点はシンプルな方が余白が多くあって、それ以降多様な観点からの作り込みを促しやすいのかもしれない。


## ver3.0での変更点（改悪含）
- 10限まで使うことがなく6限に戻し、イベントを登録するだけの単純なものへ
- 繰り返しイベントとして登録しないコードへと変更されたことを踏まえ、開始日、終了日のみの設定で動作するように
- その他、コード全体を見直して整理

## ver3.3での変更点
- コードの最適化

「[(配布用)時間割toカレンダーver3.3](https://bit.ly/41EsR1k)」をクリックし、コピーをドライブ上へ作成して使用して下さい。

```javascript:コード.gs
function onOpen() {
  let ui = SpreadsheetApp.getUi();
  let menu = ui.createMenu('時間割');
  menu.addItem('作成', 'createSchedule');
  menu.addItem('削除', 'deleteSchedule');
  menu.addToUi();
}

function createSchedule() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const startDate = new Date(sheet.getRange('A4').getValue());               
  const endDate = new Date(sheet.getRange('A6').getValue());                

  let result = Browser.msgBox(`${Utilities.formatDate(startDate, 'JST', 'yyyy-MM-dd')} ～ ${Utilities.formatDate(endDate, 'JST', 'yyyy-MM-dd')} の期間に対して、この時間割を Goolgeカレンダーに作成して良いですか？\\n 【注意】 この操作は取り消せません！`, Browser.Buttons.OK_CANCEL);
  if (result == "ok") {
    try {
      const CALENDAR_ID = sheet.getRange('B2').getValue();     
      let calendar = CALENDAR_ID ? CalendarApp.getCalendarById(CALENDAR_ID) : CalendarApp.getDefaultCalendar();

      const time_schedule = sheet.getRange('C4:D9').getValues();
      const schedule = sheet.getRange('E4:K9').getValues();

      // Compute interval in days using date objects
      const interval = Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24));
      
      for (let i = 0; i <= interval; i++) {
        let targetDay = new Date(startDate);
        targetDay.setDate(targetDay.getDate() + i);
        let weekDay = targetDay.getDay();
        if (weekDay == 0) {
          continue;
        }
        weekDay--;

        for (let j = 0; j < 6; j++) {
          if (schedule[j][weekDay] == '')
            continue;                                            

          let startTime = new Date(targetDay);
          startTime.setHours(time_schedule[j][0].getHours());
          startTime.setMinutes(time_schedule[j][0].getMinutes());
          let endTime = new Date(targetDay);
          endTime.setHours(time_schedule[j][1].getHours());
          endTime.setMinutes(time_schedule[j][1].getMinutes());

          calendar.createEvent(schedule[j][weekDay], startTime, endTime);
        }
      }
    } catch (e) {
      Browser.msgBox('エラーが発生しました: ' + e.message);
    }
  }
}

function deleteSchedule() {
  const sheet = SpreadsheetApp.getActiveSheet();
  let startDate = new Date(sheet.getRange('A4').getValue());           
  let endDate = new Date(sheet.getRange('A6').getValue());             
  endDate.setHours(23);
  endDate.setMinutes(59);

  let result = Browser.msgBox(`設定されている期間 ${Utilities.formatDate(startDate, 'JST', 'yyyy-MM-dd')} ～ ${Utilities.formatDate(endDate, 'JST', 'yyyy-MM-dd')} の予定を削除しても良いですか？\\n 【注意】 この操作は取り消せません！`, Browser.Buttons.OK_CANCEL);
  if (result == "ok") {
    const CALENDAR_ID = sheet.getRange('B2').getValue();               
    let calendar = CALENDAR_ID ? CalendarApp.getCalendarById(CALENDAR_ID) : CalendarApp.getDefaultCalendar();

    let events = calendar.getEvents(startDate, endDate);
    events.forEach(function (event) {
      event.deleteEvent();
    });
  }
}

/**
 * 取得したカレンダー情報のクリア
 */
function checkClear() {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('カレンダー情報');
  
  // 一旦、範囲をすべて削除
  sheet.clearContents();

  // 先頭行の見出しを再設定
  let headers = [
    {cell: 'A1', value: "実行アカウント"},
    {cell: 'A4', value: "登録されているカレンダーの数"},
    {cell: 'A7', value: "※このシートの"},
    {cell: 'A8', value: "　シート名は変更しない！"},
    {cell: 'C1', value: "登録されているカレンダーの ID"},
    {cell: 'D1', value: "名称"},
    {cell: 'E1', value: "isHidden"},
    {cell: 'F1', value: "isMyPrimaryCalendar"},
    {cell: 'G1', value: "isOwnedByMe"},
    {cell: 'H1', value: "isSelected"},
    {cell: 'I1', value: "Timezone"}
  ];

  headers.forEach(header => {
    sheet.getRange(header.cell).setValue(header.value);
  });
}

/**
 * カレンダー一覧の取得
 */
function getMyCalendars() {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('カレンダー情報');
  
  checkClear();
  
  // 現在の実行アカウントを確認
  let eMailID = Session.getActiveUser().getUserLoginId();
  sheet.getRange('A2').setValue(eMailID);

  // ユーザーが所有またはサブスクライブしているすべてのカレンダーを取得します。
  let calendars = CalendarApp.getAllCalendars();
  sheet.getRange('A5').setValue(calendars.length); // カレンダーの総数

  let calendarData = calendars.map((calendar, i) => {
    return [
      i + 1,
      calendar.getId(),    // C
      calendar.getName(),    // D
      calendar.isHidden(),   // E
      calendar.isMyPrimaryCalendar(),    // F
      calendar.isOwnedByMe(),    // G
      calendar.isSelected(),     // H
      calendar.getTimeZone()     // I
    ];
  });

  // 書き込み位置の開始行を定義
  let startRow = 2;

  // 全データを一度に書き込む
  sheet.getRange(startRow, 2, calendarData.length, calendarData[0].length).setValues(calendarData);
}
```