🙆‍♀️

Google Calendar の一部の予定を Spread Sheet で管理する

2024/01/30に公開

目的

Spread Sheet に予定を記述し、それを Google Calendar に反映する。
Spread Sheet 上の予定を操作(例えば削除したり、件名を更新したり)したらそれを Google Calendar に反映する。

Spread Sheet

  • 日付
    • '10/3' のように指定する
    • 空や Date Object じゃない不正な文字列は登録されない
  • 時間設定
    • '1200-1300' のように指定する
    • 空は件名の最後に(仮)が付与される
    • 不適切な設定は終日の予定として登録される
    • *(アスタリスク)は終日予定を明記するために使用(内部的には不適切な設定として処理)

GAS

Import Library

  • SpreadSheetSQL: 17p1ghyOkbWOhdE4bdBFhOXL079I-yt5xd0LAi00Zs5N-bUzpQtN7iT1a

Code

const MyCalendar = (() => {
  // アクセス可能なカレンダーのIDを指定して、Googleカレンダーを取得する
  const myGmailAdress = 'my-email-address@gmail.com';
  return CalendarApp.getCalendarById(myGmailAdress);
})();

const Columns = ['id', 'eid', 'up', '日付', '時間',	'件名', 'URL', 'メモ'];
const MySpreadSheet = (() => {
  const id = 'my-spread-sheet-id';
  const name = 'my-sheet-name';
  return SpreadSheetsSQL.open(id, name);
})();

const ListingEvents = (() => {
  return MySpreadSheet.select(Columns).result()
      .filter(object => object['日付'] != '');
})();

const CalendarEvents = (() => {
  // 取得開始日
  let startDate = new Date(2024, 0, 20);
  // 取得終了日
  let endDate = new Date(2025, 3, 31);
  // 開始日~終了日に存在するGoogleカレンダーのイベントを取得する
  let myEvent = MyCalendar.getEvents(startDate, endDate);
  return myEvent;
})();

function isInCalendar(targetEvent) {
  const descriptions = CalendarEvents
      .map(event => event.getDescription());
  for(let i = 0 ; i < descriptions.length; i++){
    const found = descriptions[i].match(/(?<=\&\&)[0-9]+/);
    if (found !== null) {
      if (targetEvent['id'] == found[0]) {
        return Number(found[0]);
      }
    }
  }
  return 0;
}

function formDateTimeSet(date, time) {
  // date は日付オブジェクト
  const options = {
    month: '2-digit', // 2桁の月 (01 - 12)
    day: '2-digit' // 2桁の日 (01 - 31)
  };
  date = date.toLocaleDateString('ja-JP', options);

  // Match Date
  const dateMatch = date
      .match(/[0-9]{1,2}\/[0-9]{1,2}/);
  if (dateMatch === null) return;
  const dateSet = (dateMatch !== null)
    ? dateMatch[0].split('/').map(str => Number(str))
    : null;

  // Match Time "0000-0000"
  const timeMatch = time
      .match(/[0-9]{4}-[0-9]{4}/);
  const timeSet = (timeMatch !== null)
    ? timeMatch[0].split('-')
        .map(str => [Number(str.substr(0,2)), Number(str.substr(2,2))])
    : null;

  return {
    dateSet, timeSet
  };
}

function formEvent(listingEvent) {
  // 件名 / title
  const title = (listingEvent['時間'] == '')
    ? listingEvent['件名'] + '(仮)'
    : listingEvent['件名'];

  // 説明 / description
  let description = '';
  if (listingEvent['メモ']) {
    description = description + `${listingEvent['メモ']}\n`;
  }
  if (listingEvent['URL']) {
    description = description + `[URL](${listingEvent['URL']})\n`;
  }
  description = description + `&&${listingEvent['id']}`;

  // イベントタイプ / type ; 0: normal, 1: allday
  // 開始・終了時間 / datetime
  let datetime = {};
  const { dateSet, timeSet } = formDateTimeSet(listingEvent['日付'],
      listingEvent['時間']);
  if (timeSet === null) {
    // 終日
    datetime = {
      type: 1,
      start: new Date(2024, dateSet[0]-1, dateSet[1]),
      end: null,
    }
  } else {
    datetime = {
      type: 0,
      start: new Date(2024, dateSet[0]-1, dateSet[1],
          timeSet[0][0], timeSet[0][1]),
      end: new Date(2024, dateSet[0]-1, dateSet[1],
          timeSet[1][0], timeSet[1][1]),
    }
  }

  return {
    title, description, datetime
  };
}

function registCalenderEvent(listingEvent) {
  // 新規
  let newEvent;
  const { title, description, datetime } = formEvent(listingEvent);
  if (datetime.type === 1) {
    // 終日
    newEvent = MyCalendar.createAllDayEvent(title,
        // 予定の開始日時
        datetime.start,
        {
          description: description
        }
        );
  } else {
    // 時間指定
    newEvent = MyCalendar.createEvent(title,
        // 予定の開始日時
        datetime.start,
        // 予定の終了日時
        datetime.end,
        {
          description: description
        }
        );
  }
  return newEvent;
}

function listScanning() {
  for(let i = 0 ; i < ListingEvents.length ; i++ ){
    const lsEvent = ListingEvents[i];
    const exist = isInCalendar(lsEvent);

    if (exist != 0 && !lsEvent['up']) {
      // 作成済み かつ 更新もオフ
      continue;
    }

    let newEvent;

    if (exist == 0) {
      // 未作成の予定を作成する
      newEvent = registCalenderEvent(lsEvent);

    } else if (lsEvent['up']) {
      // 作成済みの予定を削除し、作成する(更新)
      const targetEvent = MyCalendar.getEventById(lsEvent['eid']);
      targetEvent.deleteEvent();

      MySpreadSheet.updateRows({
        'up': false
      }, 'id = ' + lsEvent['id']);
      console.log('予定が削除されました:', lsEvent['id'], lsEvent['eid']);

      newEvent = registCalenderEvent(lsEvent);

    } else {

      continue;

    }

    // Reminder削除
    newEvent.removeAllReminders();

    // Event ID 登録
    const newEventId = newEvent.getId();
    MySpreadSheet.updateRows({
      'eid': newEventId
    }, 'id = ' + lsEvent['id']);
    console.log('予定が作成されました:', lsEvent['id'], newEventId);
  }
}

Discussion