【ご当地データ】GASで徳島市ごみ収集日のGoogleカレンダーを公開してみた

## ご当地データとは

その地域のための、その地域の市民だけに意味のあるデータを指します。

## 完成イメージ

![スクリーンショット 2022-06-13 10.52.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112294/e3c07696-9322-9adb-0a89-1d05b18747b6.png)
＊ 左：徳島市ごみ収集日のPDF版です。
＊ 右：今回生成したGoogleカレンダーです。

「ゴミ捨て当番は誰かな・・・」「今日は何のゴミかな・・・」という時に、このカレンダーを連携しておくと、スマホに通知が来て便利です。

徳島市は、ごみ収集日が少ないです。例えば、ダンボールの収集日が月に1回しかないため、忘れてしまうと1ヶ月ゴミが溜まってしまうという辛い状況が発生します。なので、ゴミの日は重要な問題なのです。

## 使ってみる

- 徳島市**A地区**にお住みの方
    - [Googleカレンダーに連携する](https://calendar.google.com/calendar/u/0?cid=Y191amNrYms4MjM2bThqaDYxOXNwbm9mZzI2MEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t)
- 徳島市**B地区**にお住みの方
    - [Googleカレンダーに連携する](https://calendar.google.com/calendar/u/0?cid=Y19pYzBmZXZjcXNqbDBucDI4Z2Iza2c1OGlya0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t)
- 徳島市**C地区**にお住みの方
    - [Googleカレンダーに連携する](https://calendar.google.com/calendar/u/0?cid=Y19mMDZvYWk1cm1kZjVkMW04MWtxcWxzMzZhc0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t)
- 徳島市**D地区**にお住みの方
    - [Googleカレンダーに連携する](https://calendar.google.com/calendar/u/0?cid=Y19qcGUwOTA3NHRycDdnMjZwZDhnc2ExbTA1OEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t)

## 主なリソース

- [徳島市ごみ収集日のHP](https://www.city.tokushima.tokushima.jp/smph/kurashi/recycle/gomi/r4gomischedule.html)
    - A, B, C, D地区の説明と収集日程が記載されている。
- [Parserライブラリ](https://script.google.com/home/projects/1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw/edit)
    - Google Developer Expert (GDE)の[Ivan Kutil氏](https://cz.linkedin.com/in/ivankutil)が作成したWebスクレイピング用のライブラリ
    - 参考記事： [GASでスクレイピングする方法！Parserライブラリを利用した手順を解説](https://auto-worker.com/blog/?p=2460)
- [UrlFetchAppクラス](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app)
    - GASで、HTTPリクエスト(GET/POSTなど)に使うクラス
- [CalendarAppクラス](https://developers.google.com/apps-script/reference/calendar/calendar-app)
    - GASで、Googleカレンダーを操作するのに使うクラス
- [Utilitiesクラス](https://developers.google.com/apps-script/reference/utilities/utilities)
    - 便利関数を集めたクラス
    - 今回は、Utilities.sleep()という処理を指定した時間停止する関数を使いました
- [Dateクラス](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date)
    - JavaScriptの日付クラス。
    - 参考記事：[日付 と 時刻](https://ja.javascript.info/date)

## 【完成品】 GASスクリプト

```javascript
// 徳島市の令和4年度家庭ごみ収集日程表HP
const TOKUSHIMA_URL = "https://www.city.tokushima.tokushima.jp/smph/kurashi/recycle/gomi/r4gomischedule.html";

// 対象年度
const YEAR = 2022;

/**
 * 徳島市_A地区_ごみ収集のGoogleカレンダーを作成します。
 */
function createAreaA() {
  const areaId = 'cmslabel09'
  const calendarId = 'c_ujckbk8236m8jh619spnofg260@group.calendar.google.com';
  //0：日、1：月、2：火、3：水、4：木、5：金、6：土
  const burnableDays = [1, 4];
  const rows = makeGomiDateList_(areaId, burnableDays);
  createEvents_(calendarId, rows);
}

/**
 * 徳島市_B地区_ごみ収集のGoogleカレンダーを作成します。
 */
function createAreaB() {
  const areaId = 'cmslabel10'
  const calendarId = 'c_ic0fevcqsjl0np28gb3kg58irk@group.calendar.google.com';
  const burnableDays = [1, 4];
  const rows = makeGomiDateList_(areaId, burnableDays);
  createEvents_(calendarId, rows);
}

/**
 * 徳島市_C地区_ごみ収集のGoogleカレンダーを作成します。
 */
function createAreaC() {
  const areaId = 'cmslabel11'
  const calendarId = 'c_f06oai5rmdf5d1m81kqqls36as@group.calendar.google.com';
  const burnableDays = [2, 5];
  const rows = makeGomiDateList_(areaId, burnableDays);
  createEvents_(calendarId, rows);
}

/**
 * 徳島市_D地区_ごみ収集のGoogleカレンダーを作成します。
 */
function createAreaD() {
  const areaId = 'cmslabel12'
  const calendarId = 'c_jpe09074trp7g26pd8gsa1m058@group.calendar.google.com';
  const burnableDays = [2, 5];
  const rows = makeGomiDateList_(areaId, burnableDays);
  createEvents_(calendarId, rows);
}

/**
 * ごみ収集日程の配列を生成します。
 * @param {String} areaId エリアID
 * @param {String} calendarId GoogleカレンダーID
 * @param {Number[]} burnableDays 燃えるゴミの曜日
 */
function makeGomiDateList_(areaId, burnableDays) {
  const html = UrlFetchApp.fetch(TOKUSHIMA_URL).getContentText("UTF-8");

  //Parserライブラリを使ってスクレイピング
  const tablesHtml = Parser.data(html)
    .from(`<div id="${areaId}" class="h3bg">`)
    .to('</table>')
    .build();
  const bodys = Parser.data(tablesHtml)
    .from('<tr>')
    .to('</tr>')
    .iterate();

  // 燃えるゴミ日は、毎週決まった曜日なので
  // まずは、燃えるゴミ日の配列を作っておく。
  // 燃えるゴミ日の配列に他のゴミの日も追加する。
  const rows = createBurnableRows_(burnableDays);
  for (const bodyhtml of bodys) {
    const values = Parser.data(bodyhtml)
      .from('<td class="left">')
      .to('</td>')
      .iterate();
    let index = 0;
    let title = '';
    for (const value of values) {
      const value2 = value.replace('<p>', '').replace('</p>', '')
      // 1列目は分別区分
      if (index === 0) {
        title = value2;
        index += 1;
        continue;
      }
      // 2列目以降は、月別+分別区分ごとの収集日
      if (value2.includes(',')) {
        // 「6,20」のように1セルに2日分のデータが入力されているケース
        const dates = value2.split(',')
        for (const date of dates) {
          rows.push(createRow_(index, title, Number(date)));
        }
      } else {
        // 「6」のように1セルに1日分のデータが入力されているケース
        rows.push(createRow_(index, title, Number(value2)));
      }
      index += 1;
    }
  }
  return rows;
}

/**
 * ゴミの日1行のデータを作成する
 * @return {Object[]} ゴミの日1行の配列。[分別区分, 開始時刻, 終了時刻]
 */
function createRow_(index, title, date) {
  const row = []
  let year = YEAR;
  let month = 0;
  if (index >= 10) {
    // 来年の場合
    month = index - 9;
    year += 1;
  } else {
    // 今年の場合
    month = index + 3;
  }
  // monthは1ヶ月前を指定するのに注意。例えば、3月ならmonth = 2とする。
  const startTime = new Date(year, month - 1, date, 8, 0);
  const endTime = new Date(startTime.getTime());
  endTime.setMinutes(endTime.getMinutes() + 30);
  row[0] = title;
  row[1] = startTime;
  row[2] = endTime;
  return row;
}

/**
 * Googleカレンダーにイベント(ゴミの日)を作成します。
 * @param {String} calendarId GoogleカレンダーID
 * @param {Object[][]} rows [][title,startTime,endTime]
 */
function createEvents_(calendarId, rows) {
  const calendar = CalendarApp.getCalendarById(calendarId);
  const start = new Date(YEAR, 3, 4, 8, 0)
  const end = new Date(YEAR + 1, 2, 31, 8, 0)
  const allEvents = calendar.getEvents(start, end);

  // 不要なイベントが登録されている可能性があるため
  // 全てのイベントを消しておく。
  for (const event of allEvents) {
    event.deleteEvent();
    Utilities.sleep(500)
  }

  // Googleカレンダーにイベントを登録する。
  for (const row of rows) {
    const title = row[0];
    const startTime = row[1];
    const endTime = row[2];
    calendar.createEvent(
      title,
      startTime,
      endTime
    );
    Utilities.sleep(500)
  }
}

/**
 * 燃やせるゴミイベントの配列を生成します。
 * 
 * @param {Number[]} burnableDays 燃えるゴミの曜日
 * @return {Object[][]}燃やせるゴミイベントの配列
 */
function createBurnableRows_(burnableDays) {
  const title = "燃やせるごみ";
  const rows = [];

  // 来年の4月1日までイベントを作成する
  const end = new Date(YEAR + 1, 3, 0, 0, 0)
  for (let start = new Date(YEAR, 3, 1, 8, 0); start < end; start.setDate(start.getDate() + 1)) {
    if (burnableDays.includes(start.getDay())) {
      // 燃えるゴミ曜日だった場合
      const endTime = new Date(start.getTime());
      endTime.setMinutes(endTime.getMinutes() + 30);
      // Calendar.createEvent(title, startTime, endTime) 
      // を意識して配列に格納する。
      const row = []
      row[0] = title;
      row[1] = new Date(start.getTime());
      row[2] = endTime;
      rows.push(row);
    }
  }
  return rows;
}
```

### 上記スクリプトの流れ

実行メソッドは、createAreaA()、createAreaB()、createAreaC()、createAreaD()の4つ。

1. **areaId, calendarId, burnableDaysを指定する**
    - areaId：収集地区を分けるためのHTMLのdivタグのID
        - `<div id="${areaId}" class="h3bg">`
    - calendarId：GoogleカレンダーのID
        - ![スクリーンショット 2022-06-13 12.00.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112294/5aa326f7-6ba8-1f67-546a-ae6350b01e7b.png)
        - burnableDays：燃えるゴミの日の曜日
            - 0：日、1：月、2：火、3：水、4：木、5：金、6：土
1. **ごみ収集日程の配列を生成する(`makeGomiDateList_メソッド`)**
    1. Parserライブラリを使って、徳島市の令和4年度家庭ごみ収集日程表HPをWebスクレイピングする。
    1. 燃えるゴミ日の配列を作成する(`createBurnableRows_メソッド`)
    1. Webスクレイピングの結果から、`[分別区分, 開始時刻, 終了時刻]`でゴミ日程の配列を作成する。
1. **Googleカレンダーのイベントを作成する(`createEvents_メソッド`)**
    1. 不要なイベントが登録されている可能性があるため全てのイベントを消しておく。
    1. Googleカレンダーにゴミの日を登録する。 

## 実装コツ・ポイント

- WebスクレイピングにはParserライブラリが便利です。
- 燃えるゴミと、燃えるゴミ以外でデータの作成を分けることで、ロジックをわかりやすくしてます

## このご当地データの生い立ち

GASのスキルを活かして地域貢献したいと思ったことがキッカケです。
徳島市民だけに意味のあるデータ（ご当地データ）を公開できたら嬉しいなと思って作成しました。

## [宣伝]Udemyの自作教材

私は、2021年7月に[wywy合同会社](https://wywy.jp/)という会社を起業しました。
Qiita記事をキッカケに知名度を少しでも上げたいので、以下自作のUdemyを宣伝させていただければです。

**■ Udemy**
GoogleアカウントとWebブラウザがあれば開発準備OK！これからプログラミングを始める方も取り組みやすいように「40のスキル」を選定。受講後、Googleアプリ連携による業務効率化ができるようになります。
`2022年6月時点で、約1500人の受講生を獲得し、UdemyBusinessに選定されてます。`

https://www.udemy.com/course/wywy-google-apps-script-forty-skills/?referralCode=7F71969F9DD9849220B4