Hero

ゴミ収集日のリマインダーを作る。

Date: 2022/04/10 12:57

Category: none

はじめに

全ての予定はGoogleカレンダーなどのスマホで確認したい派の人間にとって、いまだに紙に印刷されたスケジュール表という形を採用している「ゴミ収集日カレンダー」は厄介者である。

そこで53cal.jpというゴミ収集日をメールで事前に教えてくれるサービスを用いて、Googleカレンダーに自動でゴミ収集日を登録してもらう、という手段を取っていた。
前日に以下のようなメールが送られてくるので、これをGASで読み取ってGoogleカレンダーに登録するという簡単なものだ。

3/30(水)は、「トレー・プラスチック容器」の収集日です。

ただ、このサービス、以下のメール文を最後にサービスが終了してしまっていた...
どうりで最近通知が来なかったわけである。

誠に勝手ながら53calは2022年3月末をもちまして、情報提供サービスを終了させていただきます。これまでのご愛顧に心より感謝申し上げます。
-----
ごみ収集日お知らせサービス53cal(ゴミカレ)

53cal.jpのような代替サービスが他にもあれば助かったのだが、そんなものは今のところ見当たらなかった。
せめてゴミ収集日の情報をAPIで取得できないかも調査したが、少なくとも私が住んでいる地域ではそのようなものは見当たらなかった。

とりあえず、ゴミ収集日のカレンダーを自動的にGoogleカレンダーに登録できさえすればいいので、
その方法を考えることにする。

ゴミ収集日について知る。そして記述する。

私が住んでいる地域ではどうやら、毎週月曜日は紙ゴミの日、などといった感じで、曜日ごとに収集されるゴミが決まっていた。また、1回目の月曜日はペットボトルの日、といった感じで、何回目の何曜日か、というところでも決まっているらしい。

つまり、ゴミ収集日のルールは、以下の3変数で記述できることになる。

  1. ゴミの種類
  2. 曜日
  3. タイミング(1回目, 2回目, ... 5回目)

たとえば、毎週月曜日が燃えるゴミの日であれば、ゴミの種類=燃えるゴミ,曜日=月曜日,タイミング=1回目,2回目,3回目,4回目,5回目という感じだ。

ここまで書き下すことができれば、あとは従来通りGASでGoogleカレンダーに追加できるようにJSON形式などにして、それを読み取り自動でカレンダーを追加できるようにすればいい。

1"燃えるゴミ": [ 2 { 3 "dayOfWeek": "月", 4 "timing": [ 5 1,2,3,4,5 6 ] 7 } 8 ], 9

ゴミの種類によっては、月曜日と金曜日のように週に2回収集するものもあるので、以下のように複数定義されることも想定しておく。(以下は一例である)

1"燃えるゴミ": [ 2 { 3 "dayOfWeek": "月", 4 "timing": [ 5 1,2,3,4,5 6 ] 7 }, 8 { 9 "dayOfWeek": "金", 10 "timing": [ 11 1,2,3,4,5 12 ] 13 }, 14 ], 15

ゴミ収集日の定義に従ってリマインダーをセットする。

今回の問題は、JSON形式で定義された、いくつかの曜日とタイミングの入った配列を読み取り、それに対応する日付を得るものと考えていい。

もっと噛み砕いて言えば、特定の月において、月曜日の日付を全て取得してきて、1回目の月曜日だったら、その日付の1番目を返してあげさえすれば達成できる。

以下のステップを踏むことでこれを実装した。詳細はコードを見てもらえればわかると思う。

  1. その月の初日の曜日を取得する
  2. その月の最終日を翌月の1日前の日付を求めて特定し、その月が何日あるのかを把握した
  3. range()を定義してその月の最初の各曜日の日付から、曜日ごとの日付配列を作成した
  4. 作成した曜日ごとの日付配列をゴミの種類ごとに定義したJSONを読み取り、対応する日付を求めた
<details><summary>書いたGASのコード</summary>
1//https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global*Objects/Array/from 2const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (*, i) => start + (i * step)) 3const weeks = \["日","月","火","水","木","金","土"] 4const NOTIFICATION_HOUR = 7 5const NOTIFICATION_MINUTES = 30 6const PopupReminderTheDayBefore = 720 // 12時間前 7let props = PropertiesService.getScriptProperties() 8const CALENDAR_ID = props.getProperty('CALENDAR_ID') 9 10function main(){ 11 let calendars = getDustCalendars() 12 for(let \[name,value] of Object.entries(calendars)) { 13 Logger.log(name) 14 Logger.log(value) 15 let calendar = CalendarApp.getCalendarById(CALENDAR_ID) 16 17 value.map(date=>{ 18 // 開始時刻 19 let startDate = new Date(date) 20 // 終了時刻 21 date.setHours(NOTIFICATION_HOUR+1) 22 let endDate = new Date(date) 23 24 if (startDate < new Date()) return 25 // 既に登録済みなら終了 26 let events = calendar.getEvents(startDate, endDate) 27 28 for(var i=0; i<events.length; i++) { 29 if (events[i].getTitle() === '[ゴミカレンダー] '+name) { 30 return 31 } 32 } 33 34 let event = calendar.createEvent("[ゴミカレンダー] "+name,startDate,endDate) 35 event.setDescription("ゴミカレンダー") 36 event.addPopupReminder(5) 37 event.addPopupReminder(30) 38 event.addPopupReminder(720) 39 Logger.log('Event ID: ' + event.getId()) 40 }) 41 Utilities.sleep(2000) 42 } 43} 44 45function deleteCalendars() 46{ 47 let calendars = getDustCalendars() 48 for(let \[name,value] of Object.entries(calendars)) { 49 Logger.log(name) 50 Logger.log(value) 51 let calendar = CalendarApp.getCalendarById(CALENDAR_ID) 52 53 value.map(date=>{ 54 // 開始時刻 55 let startDate = new Date(date) 56 // 終了時刻 57 date.setHours(NOTIFICATION_HOUR+1) 58 let endDate = new Date(date) 59 60 // 既に登録済みなら削除 61 let events = calendar.getEvents(startDate, endDate) 62 63 for(var i=0; i<events.length; i++) { 64 if (events[i].getTitle() === '[ゴミカレンダー] '+name) { 65 events[i].deleteEvent() 66 return 67 } 68 } 69 }) 70 } 71} 72 73// ゴミの種類ごとにゴミ収集日をDate形式で返す 74function getDustCalendars() { 75 let month = generateMonthMatrixFromToday() 76 // definition.json.gsを読み込む 77 let def = getJsonForDustCalendar()\[0] 78 79 let dustCalendars = {} 80 for(key in def){ 81 // ゴミの種類ごとに取得する 82 let result = def\[key].map(confing=>{ 83 // 曜日に対応する日の配列を取得する 84 let datesFromdayOfWeek = month\[weeks.indexOf(confing.dayOfWeek)] 85 // timingに指定された数だけ取得する 86 return confing.timing.map(_timing=>{ 87 // x週目の日付を取得 88 return datesFromdayOfWeek.slice(_timing-1,_timing)\[0] 89 }).filter(v=>v) 90 }).flat().sort((a,b)=>a-b) 91 // ゴミの種類ごとにゴミ収集日をDateで保存する 92 dustCalendars\[key] = result.map(v=>{ 93 let date = new Date() 94 date.setDate(v) 95 date.setHours(NOTIFICATION_HOUR) 96 date.setMinutes(NOTIFICATION_MINUTES) 97 date.setSeconds(0) 98 return new Date(date) 99 }) 100 } 101 return dustCalendars 102} 103 104function generateMonthMatrixFromToday() 105{ 106 let month = {} 107 let date = new Date() 108 const firstDate = getFirstDate(date) 109 const days = getLastDate(date).getDate() 110 const weekOfDay = firstDate.getDay() 111 112 // 初日の曜日から順番に日付を求めていく 113 let _weekOfDay = weekOfDay 114 for(let d=1;d<=7;d++) { 115 month\[_weekOfDay] = getDateFromWeekOfDays(d, days) 116 _weekOfDay += 1 117 _weekOfDay = _weekOfDay%7 118 } 119 return month 120} 121 122// その指定された日のn週間後の日付を月末まで求めて配列で返す 123function getDateFromWeekOfDays(numberOfFirstDate,daysOfMonth) 124{ 125 return range(numberOfFirstDate,daysOfMonth,7) 126} 127 128// 月初の日付を生成 129function getFirstDate(date) 130{ 131 date.setDate(1) 132 return new Date(date) 133} 134 135// 月末の日付を生成 136function getLastDate(date) 137{ 138 date.setMonth(date.getMonth()+1) 139 date.setDate(1) 140 date.setDate(date.getDate()-1) 141 return new Date(date) 142} 143
</details>

実際に登録されたカレンダー

まとめ

53cal.jpで今まで得ていたゴミ収集日カレンダーを、サービス終了をきっかけに多少アナログだがゴミ収集日の情報をJSON形式に変換した。
そのデータをもとにゴミ収集日を取得し、GoogleCalendarに登録するGASを書いた。
事象を観察し一般化することで、自動化(プログラム)に落とし込めるということを改めて実感した例だった。

Hero

まさき。です。PHPエンジニアをやってます。

自分の課題を技術で乗り越えるの好きかもしれないです。

フロントエンドは苦手ですが、少しでもできるようになれたらな、ということでNextJSでこのブログサイトを作りました。