LoginSignup
2
1

More than 1 year has passed since last update.

[Google Apps Script] カレンダーファイルを生成する

Last updated at Posted at 2022-11-29

バイトのシフトなど、予定を手入力でカレンダーアプリに登録していませんか?
私のバイト先ではシフトがexelの表で公開されるので、毎回手入力でカレンダーに登録していました。そして登録漏れによってバイトを欠席してしまった事も...

「Exelでデータがあるなら、カレンダーファイルを送れ!!」

ということでスマホやパソコンのカレンダーアプリに予定を追加できるカレンダーファイル(.ics)についての備忘録です。

できること

カレンダーファイルを生成し、ダウンロードURLを生成する。
LINEやメールに添付することでシフト管理などが容易になる。

実行環境

Google Apps Script
JavaScript

カレンダーファイルとは

テキストファイル(.txt)の拡張子を.icsに変えただけです。
ファイルの中身は以下のtxtファイルのように、
  BEGIN:タグ
     〜
  END:タグ
構造をとっています。

calender.txt
BEGIN:VCALENDAR

PRODID:-//sample//sample//JP
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VTIMEZONE
TZID;X-RICAL-TZSOURCE=TZINFO:Asia/Tokyo
BEGIN:STANDARD
DTSTART:19510908T020000
RDATE:19510908T020000
TZOFFSETFROM:+1000
TZOFFSETTO:+0900
TZNAME:JST
END:STANDARD
END:VTIMEZONE

BEGIN:VEVENT
DTSTART;TZID=Asia/Tokyo;VALUE=DATE-TIME:20221020T163000
DTEND;TZID=Asia/Tokyo;VALUE=DATE-TIME:20221020T180000
ATTENDEE:
DESCRIPTION:英語4 オンライン
SUMMARY:英語オンライン
LOCATION:
END:VEVENT

END:VCALENDAR

このtxtファイルで注目していただきたい点は、
 1. txtファイル全体はVCALENDERタグで囲まれている
 2. VTIMEZONEタグで囲われている部分は編集の必要がない。おまじない
 3. VEVENTの中身が本体であり、VEVENTは複数個作って良い
これさえ知ってしまえば、あとはVEVENTを量産してtxtファイルに追記していくだけです。

以下の記事にics形式の詳細な説明があるので、是非参考にしてください。

GASでtxtファイルを作る

GASでは、とある関数に(文字列、ファイル形式、ファイル名)を送るだけでtxtファイルを作成できます。

以下のサンプルコードではtxtファイルを"text/calendar"形式で生成しています。
また、ファイルを生成しただけではプログラム終了時に消えてしまうので、folderIDで指定したGoogle Driveに保存します。

makeics.gs
const folderID = "sample-id-XXXXXXXXX";

function make_ics(data,filename){
  const blob = Utilities.newBlob(data, "text/calendar", file_name);  //ファイルを生成
  var folder = DriveApp.getFolderById(folderID);  //IDで指定したフォルダを開く
  folder.createFile(blob);  //引数file_nameで指定したファイル名でicsファイルを作成
}

上の関数の
 dataに、前述のtxtファイルのような文字列
 filenameに、"sample.ics"のような拡張子icsの文字列
を送れば、無事カレンダーファイルが生成されるというわけです。
フォルダIDがわからない場合は、こちらの記事【GAS】GoogleドライブのフォルダIDの取得方法を参考にしてください。

ダウンロードURLの取得

ダウンロードURLの取得は2ステップでできます。

1. カレンダーファイルのファイルIDを取得する

  var fileId = DriveApp.getFolderById(folderID).getFilesByName(file_name).next()

2. URLを発行する

  var url = fileId.getDownloadUrl();  //ダウンロード用のURLを生成

実際に発行したURLがこちらになります。sample.ics

注意
カレンダーファイルのダウンロードは問題ありませんが、実際にカレンダーへ追加すると削除は手作業なので、下の画像の画面を見たら「完了」を押して終了しましょう。

このように2022/10/6、2022/10/7に予定が入ったでしょうか?

ここまででカレンダーファイルの生成は完了です!

実装

では実装を見ていきましょう。
カレンダーデータは以下のような2次元配列で用意します。

function debug() {
  const data = [
    [2022, 10, 6, 17, 0, 21, 0, "sample", "サンプル"],
    [2022, 10, 7, 17, 0, 21, 0, "sample", "サンプル"]
  ]  //[yyyy, mm, dd, begin_hour, begin_min, end_hour, end_min,title,subtitle]
  console.log(make_ics("sample", data));  //make_ics(ファイル名、予定データ)
}

次にicsファイルに格納する文字列を用意します。

function make_ics(filename, data) {
  var contents = "";
  for (var i in data) {
    var yy = String(data[i][0]);
    var mm = String(data[i][1]);
    var dd = String(data[i][2]);
    var begin_h = String(data[i][3]);
    var begin_m = String(data[i][4]);
    var end_h = String(data[i][5]);
    var end_m = String(data[i][6]);
    const title = String(data[i][7]);
    const subtitle = String(data[i][8]);
    if (yy.length < 2) yy = "0" + yy;  //yyyy/mm/ddの形式にするため
    if (mm.length < 2) mm = "0" + mm;  //yyyy/mm/ddの形式にするため
    if (dd.length < 2) dd = "0" + dd;  //yyyy/mm/ddの形式にするため
    if (begin_h.length < 2) begin_h = "0" + begin_h;  //yyyy/mm/ddの形式にするため
    if (begin_m.length < 2) begin_m = "0" + begin_m;  //yyyy/mm/ddの形式にするため
    if (end_h.length < 2) end_h = "0" + end_h;  //yyyy/mm/ddの形式にするため
    if (end_m.length < 2) end_m = "0" + end_m;  //yyyy/mm/ddの形式にするため
    contents += `\nBEGIN:VEVENT\nDTSTART;TZID=Asia/Tokyo;VALUE=DATE-TIME:${yy}${mm}${dd}T${begin_h}${begin_m}00\nDTEND;TZID=Asia/Tokyo;VALUE=DATE-TIME:${yy}${mm}${dd}T${end_h}${end_m}00\nATTENDEE:\nDESCRIPTION:${subtitle}\nSUMMARY:${title}\nLOCATION:\nEND:VEVENT\n`
  }
  var body_text = `BEGIN:VCALENDAR\nPRODID:-//${productID}//${developer_name}//JP\nCALSCALE:GREGORIAN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID;X-RICAL-TZSOURCE=TZINFO:Asia/Tokyo\nBEGIN:STANDARD\nDTSTART:19510908T020000\nRDATE:19510908T020000\nTZOFFSETFROM:+1000\nTZOFFSETTO:+0900\nTZNAME:JST\nEND:STANDARD\nEND:VTIMEZONE\n${contents}\nEND:VCALENDAR`
  console.log(body_text)
  return make_url_ics(`${filename}.ics`, body_text);
}

・for文の中では、contentsに2次元配列のデータを元に作られた文字列が追加されていきます。
・for文が終わると、body_contentsに(「おまじない」と紹介したヘッダー文、for文で作ったcontents)が格納されます。
・最後にmake_url_ics関数でファイル生成・リンク取得を行えば完了です。

ちょっとした工夫

1. Google Drive内の保存先に同じ名前のファイルを増殖させない
2. iPhoneやAndroid版のLINEアプリでリンクを踏んでも正常に動作させる

以上の二つの目標を達成していきましょう。
1つ目の目標は以下のコードで実現できます

  var isreleased = folder.getFilesByName(file_name).hasNext(); //既に同じファイル名が存在する場合はtrue
  if (isreleased) folder.getFilesByName(file_name).next().setTrashed(true);  //isreleased==trueの時、現在あるファイルを削除

フォルダ内をファイル名で検索→ファイル名で対象を削除
とてもシンプルですね!

二つ目の目標はURLにパラメータを追加することで実現できます。
iPhoneやAndroidではカレンダーアプリへの追加がデフォルトブラウザ経由でしか行えません。試しにこちらのリンクをLINEやGoogle Chromeなどで開いてみてください。
https://drive.google.com/uc?id=1QJmPvJNY-77QxpYhfUUb4fMTmr0fdeER&export=download
下の画像のように、白い画面でフリーズしてしまいます。

そのため、URLのパラメータにopenExternalBrowser=1を挿入します。
URLのパラメータとは
 1. URL中の"?"の後ろの部分
 2. 複数パラメータを追加する場合は"&"で繋ぐ
 3. パラメータは"キー:値"の形で表記する

というものであり、上記のURLではidexportをキーとするパラメータが既に存在します。
ここで、openExternalBrowser=1をパラメータを先頭につけるとURLは
https://drive.google.com/uc?openExternalBrowser=1&id=1QJmPvJNY-77QxpYhfUUb4fMTmr0fdeER&export=download
となり、無事にデフォルトブラウザが開かれたと思います。
実際のコードは以下のようになります。

function defaultbrowser_url(url) {
  //LINEの場合アプリ内ブラウザで開かれるので、強制的にデフォルトブラウザで開くパラメータをURLに付与
  //iPhoneやAndroidの場合、アプリ内ブラウザからカレンダーアプリにアクセスすることができない。
  const quest_pointer = url.indexOf("?")
  const first_url = url.slice(0, quest_pointer)
  const last_url = url.slice(quest_pointer + 1)
  url = first_url + "?openExternalBrowser=1&" + last_url;
  return url
}

最後に

ソースコードはこちらに載せてあります。

GASとLINE botの連携をすると、以下の画像のようなメッセージも送れて大変便利です。

バイトのシフト管理をしている方、積極的な導入をお願いします!

初めてのQiita投稿ですが、役立つ情報を発信できればと思います。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1