Google Apps Scriptを利用したスケジュール管理

[CYBIRD Advent Calendar 2023](https://qiita.com/advent-calendar/2023/cybird) 18日目担当、の@cy-yuka-WPです。現在はイケメンシリーズの１タイトルにてサーバー兼クライアントのエンジニアを担当しています:relaxed:
前回は@cy-tatsuya-sakaiの『[【Unity】自動生成したコードを自動的にコンパイルして実行する](https://qiita.com/cy-tatsuya-sakai/private/00af7f6d2a26b3d0d17f)』でした！

# 初めに  
　今回はGoogle Apps Script（以下GAS）という、無料で利用可能な**最小限のコード**でGoogle Apps の拡張や連携を可能にできるサービスを利用した、スケジュール管理についてお話ししようと思います。
　スケジュールを管理する際、１つ２つの予定であれば問題ないですが、複数の予定を管理する際、一覧にまとめたいがカレンダーも見ることができるといいなと思うときないですか？
 こちらの記事では、『<font color="#ff2e60">**Google スプレッドシート**で一覧にして管理している予定を**Google カレンダー**でも管理できるようにするスクリプトの書き方</font>』について書いていこうと思います。


# 本題
#### １、編集内容の取得
　まずは、データの取得を行います。

    const addTask = (e) =>　{
        var changeItem = e.range.getValues();   // 編集内容を取得
    }


　変数 : **e** に編集内容についてのデータがあるので、rangeで範囲を選択し、.getValues()でデータを取得することができます。
データを複数行ではなく一行分だけ取得したい場合は.getValue()で取得できます。


　また、今のままだとファイル内のどのシートが編集されても処理が走ってしまう状態なので、シートを指定したい場合は以下のように編集したシートの名前を取得して、if文で処理を行わせたいシートかを確認する処理を入れると指定することができます。

    const addTask = (e) =>　{
        var changeItem = e.range.getValues();   // 編集内容を取得

        // evant(e)からアクティブシート名を取得
        var sheetName = e.source.getSheetName();  

        //　対象にしたいシートを絞る
        const TASK_SHEET_NAME = '【シート名】';

        //編集されたシート名と対象にしたいシート名が一致したら実行
        if(sheetName === TASK_SHEET_NAME) {
            // 処理内容
        }
    }

これで、処理を行うシートを指定して編集内容を取得することができるようになりました:ok_hand:
    

#### ２、処理の実行範囲の絞り込み

 次に、今のままだとシートのどの部分を編集してもスケジュールを作成する処理を行おうとしてしまい、エラーが発生してしまうか、不要なスケジュールが作成されてしまうため、今回は<font color="#ff2e60">**終了日を入力時のみ**</font>スケジュールの作成を実行するようにします。

![スクリーンショット 2023-12-08 16.37.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2987965/b3844b4d-61de-3085-b6b1-69771e3cc69d.png)

今回は、上記画像のような表を使って説明をしていこうと思います。

    const addTask = (e) =>　{
        const endDayCol = 4; // 終了日の列番号

        var changeItem = e.range.getValues();   　　// 編集内容を取得
        var eCol = e.range.getColumn();          // 編集されたセルの一番左の列
        var numECol = e.range.getNumColumns();   // 編集された範囲の列数取得

　まず、編集された範囲内に終了日の列が含まれているかを確認するために終了日を入力する列を指定します。
 .getColumn()や.getNumColumns()は数値で何列目かを返してきます。
 また、.getValues()ではデータを配列で取得するため、列を指定する際もアルファベットの文字ではなく数字を使用しています。

        // 編集された列数分ループさせる
        for(var plusCol = 0; plusCol < numECol; plusCol++) {
            // 終了日の列であれば処理を行う
            if(eCol + plusCol === ndDayCol) {
                // 処理内容を記載
            }
        }
    }

　そして、編集された列数分ループを回して、編集された範囲内に終了日の列が含まれているかを確認します。
 これで、シート全体ではなく、終了日を入力した列が編集された時のみ処理が行われるようにすることができました:ok_hand:

#### ３、必要なデータの取得

　次に、スケジュールを作成するのに必要なデータの取得を行います。
 以下内容はひとつ前の項目のif文の中の処理内容になっています。
 
    // 各項目の列番号
    const taskNameCol = 1;  // タスク名の列
    const startDayCol = 3;  //　開始日の列

    var eRow = e.range.getRow();         // 編集されたセルの一番上の行
    var numERow = e.range.getNumRows();  // 編集された範囲の行数取得

    var sheet = e.source.getActiveSheet();  // 編集されたシート
    var taskRange = "A" + eRow + ":F" +  (eRow + numERow + 1); // 必要なタスクデータの範囲
    var taskData = sheet.getRange(taskRange).getValues();   // タスクデータの取得

　編集内容だけでは情報が足りないため、編集したシートから必要な範囲を指定して、データを取得する必要があります。
 .getRangeで範囲を指定する際は **列(アルファベット文字) + 行（数字）** で指定します。
 また、.getValues()は重い処理なので、ループ内で使用してしまうとパフォーマンスが下がってしまうため、この段階で取得しておいた方が良いです。

    // 編集された行数分処理を行う
    for(var plusRow = 0; plusRow < numERow; plusRow++) {
        var startDay = taskData[plusRow][startCol];   　　　　　// 開始日
        var endDay = taskData[plusRow][endCol];       　　　　　// 終了日
              
        // 開始日と終了日が入っている時のみ実行
        if(startDay != '' || endDay != '') {
            var taskTitle = taskData[plusRow][taskNameCol];　　　// タスク名
            
            // ----------------------------------
            // スケジュール作成の処理を書く
            // ----------------------------------
        }
    }

　編集された行数分処理を行います。
 ただし、開始日または終了日が設定されていなければ処理を行わないようにします。
 これで、カレンダーにスケジュールを登録するに必要なデータの取得が完了しました:ok_hand:

#### ４、カレンダーに登録

　最後に、カレンダーに予定を登録します。
 あともう少しで完成です:muscle:

        if(startDay != '' || endDay != '') {
            var taskTitle = taskData[plusRow][taskNameCol];　　　// タスク名
            
            const TASK_CALENDAR_URL = '【カレンダーID】';
            var calendar = CalendarApp.getCalendarById(TASK_CALENDAR_URL);  

            // スケジュール作成
            calendar.createAllDayEvent(
                scheduleTitle,  // スケジュール名
                startDay,　　　　　　　　　　　　　　// 開始日
                endDay,         // 終了日
                (オプションをつけることも可能。省略可)
            )
        }

　先ほどの項目のif文が通った後の部分に続きを書いていきます。
 まずは、カレンダーID利用して登録したいカレンダーを取得します。
 カレンダーIDは **カレンダーの設定** → **カレンダーの統合** で確認することができます。
 そして、取得したカレンダーに.createAllDayEvent()でスケジュールを登録します。
 ただ、終日の予定が作成されるので、終了日をこのまま登録するとカレンダーには一日前の日付までが登録されてしまうので、以下のようにデータを修正すると一覧と同じ終了日で設定ができます。
 
        endDay = new Date(endDay.setDate(endDay.getDate() + 1));

　これで、Google スプレッドシート登録した予定をカレンダーに登録することができました！！
 無事完成です:tada:

#### ５、まとめと少しおまけ
 これまでの項目で説明したコードをまとめると以下のようなコードが完成します。

<details><summary>完成したコード</summary>

    const addTask = (e) => {
        var changeItem = e.range.getValues();   // 編集内容を取得
    
        // evant(e)からアクティブシート名を取得
        var sheetName = e.source.getSheetName();  
    
        //　対象にしたいシートを絞る
        const TASK_SHEET_NAME = '【シート名】';
    
        //編集されたシート名と対象にしたいシート名が一致したら実行
        if(sheetName === TASK_SHEET_NAME){
    
            const endDayCol = 4; // 終了日の列番号
    
            var changeItem = e.range.getValues();   　　// 編集内容を取得
            var eCol = e.range.getColumn();          // 編集されたセルの一番左の列
            var numECol = e.range.getNumColumns();   // 編集された範囲の列数取得
    
            // 編集された列数分ループさせる
            for(var plusCol = 0; plusCol < numECol; plusCol++) {
                // 終了日の列であれば処理を行う
                if(eCol + plusCol === ndDayCol) {
                    // 各項目の列番号
                    const taskNameCol = 1;  // タスク名の列
                    const startDayCol = 3;  //　開始日の列
    
                    var eRow = e.range.getRow();         // 編集されたセルの一番上の行
                    var numERow = e.range.getNumRows();  // 編集された範囲の行数取得
    
                    var sheet = e.source.getActiveSheet();  // 編集されたシート
                    var taskRange = "A" + eRow + ":F" +  (eRow + numERow + 1); // 必要なタスクデータの範囲
                    var taskData = sheet.getRange(taskRange).getValues();   // タスクデータの取得
            
                    // 編集された行数分処理を行う
                    for(var plusRow = 0; plusRow < numERow; plusRow++) {
                        var startDay = taskData[plusRow][startCol];   　　　　　// 開始日
                        var endDay = taskData[plusRow][endCol];       　　　　　// 終了日
              
                        // 開始日と終了日が入っている時のみ実行
                        if(startDay != '' || endDay != '') {
                            endDay = new Date(endDay.setDate(endDay.getDate() + 1));
                            var taskTitle = taskData[plusRow][taskNameCol];　　　// タスク名
                
                            const TASK_CALENDAR_URL = '【カレンダーID】';
                            var calendar = CalendarApp.getCalendarById(TASK_CALENDAR_URL);  
    
                            // スケジュール作成
                            calendar.createAllDayEvent(
                                scheduleTitle,  // スケジュール名
                                startDay,　　　　　　　　　　　　　　// 開始日
                                endDay          // 終了日
                            )
                        }
                    }
                }
            }
        }
    }

</details>

　また、今回の内容と併せて使うのに知っていると便利なものを以下にまとめてみたので、よければ参考にしてみてください:grinning:

<details><summary>おまけ</summary>

・プロパティの取得
　　カレンダーIDのようなあまり人には知られたくないが使わなくてはいけないデータってあるじゃないですか:frowning2:
  そういうときに便利なのがプロパティ取得です。

    const TASK_CALENDAR_URL = PropertiesService.getScriptProperties().getProperty('【カレンダーIDのプロパティ名】');

　あらかじめプロパティにカレンダーIDをせってしておき、上記のように書くことでコードにベタ書きをしないのでより安全にデータを扱うことができます。

 ・.createAllDayEvent()のオプション
 　　スケジュールを作成する際に開催場所や説明などを設定する際に使用します。

<table>
  <caption>オプション一覧</caption>
    <thead>
        <tr>
            <th>項目</th> <th>内容</th>
        </tr>
    </thead>
    <tr>
        <td> description </td> <td>スケジュールの説明</td>
    </tr>
    <tr>
        <td> location </td> <td>開催予定地</td>
    </tr>
    <tr>
        <td> guests </td> <td>ゲストとして追加するメールアドレスリスト</td>
    </tr>
    <tr>
        <td> sendInvites </td> <td>招待メール送信の有無(デフォルトは送信しない)</td>
    </tr>
</table>

・特定の日時を指定してのスケジュールの設定。
　　スケジュールの設定は終日だけではなく、もちろん日時を指定しての設定も可能です。
   
    .createEvent(
        `【スケジュール名】`,
        【開始日時】,　　　　　　　　　　　//　時間も必要
        【終了日時】,     //　時間も必要
        (オプション。こちらでも省略可)

</details>

# 最後に
　今回の記事はいかがだったでしょうか？GASで一手間加えることで、Google Appsがより便利になるのですが、自分でスクリプトを書く分、始めるには少しハードルが高いかもしれませんが、使えるようになるととても便利なものなので、この記事を読んで気になった方はぜひ色々調べて学んでいただけたらと思います！:sparkles:
　明日は19日目！@gongon282828さんの『[ECS Fargateでのx86_64とarmのマルチアーキテクチャ実装概略とTerraformでの構築
](https://qiita.com/gongon282828/private/e1e852c77e242c22299b)』です！お楽しみに！