見出し画像

#18 カレンダーに書き出すプログラムを解説

GAS に取り組んでみようと考えている人が、取り組みやすいようにと考えて、以下の記事で取り上げた「#04 スプレッドシートの内容をカレンダーに書き出す」のプログラムのベースになった、「工業教育に関する情報を発信│工業教育.net」の Tatsuya さんが作成されたプログラムを解説してみます。

ベースになったプログラム

ベースとなったプログラムは、以下のような 31行のプログラムです。
以降の説明では、根拠となっているページなどもリンクしてあるので、必要に応じて参照してください。

1 ~ 4行目 冒頭のコメント

// 作成日:2021/4/24
// 作成者:Tatsuya
// URL:https://kogyo-kyoiku.net

この部分は、プログラムの説明が書かれている部分になります。
行の中で // 以降に書かれている内容はコメントとして扱われるので、この部分はプログラムとして実行されません。コメントは、このように // で 1行ずつ書くだけでなく /**/ の間に記述しても構いません。/**/ の間であれば、以下のように複数行にわたってコメントを書けます。

/*
 * 複数行に
 *    わたってコメントを書く
 */

このように、どのようなプログラムなのかを自分のための記録としてだけでなく、このプログラムを使う人のためにも、コメントとして書き記しておくことは大切なことだと思います。
4行目の空行(何もない行)は、プログラムの動作に影響しない、なんでもない行ですが、プログラムを読みやすくするための間(ま)のようなものだと思います。このような読みやすさを意識した空行も、意味のあるものだと思います。

5行目 関数の宣言

function Convert2Calender() {

この行では、このプログラムの中心となっている Convert2Calender という名前の関数を宣言(定義)しています。作成した GAS のプログラムを実行するときには、この Convert2Calender という名前で、関数を呼び出すことになります。
関数は、Microsoft Excel や Google スプレッドシートの SUM 関数のように、引数(パラメータ)を与えて、目的の処理を行ってくれるものです。今回の Convert2Calender には引数がないので、関数名の Convert2Calender のあとの () の中には、何も書かれていません。
行末の { は、プログラムの終わり 31行目にある } と対応しているものです。

6 ~ 7行目 現在開いているシートを取得する

  // 現在開いているシートを取得する
  const ActiveSheet = SpreadsheetApp.getActiveSheet();

ここからプログラムの処理が実際にはじまります。
6行目は、7行目のプログラムの説明として書かれているものです。
7行目は、「現在開いているシートを取得する」と説明されていますが、これは「4月」から「3月」まで用意されている、どのシートから呼び出されたのかを取得しています。この記述は、スプレッドシートを扱う GAS のプログラムでは当たり前のように、プログラムのはじめの部分で書かれています。

行頭にある const は、変数を宣言するときに使うものですが、var や let と違い、宣言時に初期化した後は、変数の内容が変更できないようにするものです。変更されることのない定数などを宣言するときに使用します。
const で宣言されているので、変数 ActiveSheet は、この後のプログラム中で他の値を代入できなくなっています。

続けて書かれている = は、プログラムでは代入を意味する記号です。算数や数学のように、= の両側が等しいことを示すものではありません。

その後の SpreadsheetApp は、GAS のプログラム中でスプレッドシートに対する処理を行うときに使用するクラスです。
クラスというのは、以下のサイトでも説明されていますが、厳密に理解しようとすると結構難しいです。とりあえず、「命令や変数のかたまり」と思っておきましょう。今回の場合、SpreadsheetApp というクラスを使って、スプレッドシートに対する処理が行えます。

今回の SpreadsheetApp というクラスについての説明は、Google が以下のようにリファレンスを用意しています。細かなことは、このページで確認できますが、こういったリファレンスは基本的に英語で提供されています。
とは言え、Google 翻訳などを利用すれば、英語は障害になりません。

SpreadsheetApp に続く .getActiveSheet() は、現在アクティブになっているシートを取得するための関数ものです。この関数によって、Sheet というシートを扱うためのクラスが得られます。似たような名前ですが、SpreadsheetApp は「Google スプレッドシート」、Sheet は一つの「シート」を指しているものです。
この行を実行して得られた ActiveSheet を使って、現在のシートの内容にアクセスできるようになります。

行末の ; は、日本語の「。」に相当するもので、それぞれの命令の終わりに書きます。

また、この 6行目からプログラムの先頭に空白が追加されています。これは、5行目の { と、31行目にある } に囲まれた範囲であることをわかりやすくするために字下げされているものです。これもプログラムをわかりやくして、間違い(バグ)が入り込まないようにするための配慮です。

8 ~ 9行目 現在開いているシートの月を取得する

  // 現在開いているシートの月を取得する
  const ActiveMonth = ActiveSheet.getName().replace("月","");

9行目では、以降で変更できない定数として ActiveMonth を宣言しています。ActiveMonth には、ActiveSheet.getName() によって、現在アクティブになっているシート(ActiveSheet)のシート名を文字列として取得します。
その後ろに書かれている .replace("月",""); は、JavaScript の文字列に対する関数で文字列の置換を行っています。文字列の中にある、一つ目の引数(この場合は "月")を、二つ目の引数(この場合は ””)に置き換えていますので、「4月」や「12月」といったシート名を、「4」「12」としています。

この 9行目の命令が実行された後に、ActiveMonth  の変数に格納されているのは、プログラムが実行されたときにアクティブになっていたシートの名前から ”月” を削除した「数字の文字列」となります。
「数値」なのか「文字列」なのかは細かいことですが、ひとまず補足しておきます。

10 ~ 11行目 現在開いているシートのデータを二次元配列で取得する

  // 現在開いているシートのデータを二次元配列で取得する
  let SheetData = ActiveSheet.getDataRange().getValues();

11行目は、現在開いているシートのデータを、二次元配列として一括して取得しています。取得した内容は SheetData という変数に格納されます。この変数は、後から変更される可能性があるので、const ではなく let で宣言されています。
二次元配列については、以下のサイトでの説明もご覧いただくと、理解が進むと思います。

.getDataRange() という部分は、現在開いているシート ActiveSheet である「Class Sheet」に用意されている関数で、指定されたセルの範囲「Class Range」を取得しています。getDataRange() には引数をせず、シート内のすべての範囲を取得しますが、いくつかのパラメータを指定して範囲を取得する getRange() も用意されています。

.getValues(); という部分は、「Class Range」に用意されている関数(getValues())で、その範囲のセルの内容を二次元配列として取得しています。

この命令によって、SheetData という二次元配列に、シート内のセルの内容が保存され、21行目にあるような SheetData[i][j] とすることで、指定されたセルの内容を参照できます。

このようにすべてのセルの内容を一括して取得しているのは、個別にセルの内容を取得していると、実行時間がかかってしまうからです。以下のように個別にセルを指定して取得するのではなく、一括して取得することで処理時間の短縮化を図っています。

let CellA1 = ActiveSheet.getRange("A1").getValue();

12 ~ 13行目 年度(1行A列のデータ)を取得する

  // 年度(1行A列のデータ)を取得する
  let TargetYear = SheetData[0][0];

13行目では、二次元配列 SheetData に格納されているセルの内容から、セル A1 の内容を変数 TargetYear に代入します。二次元配列の一つ目の [0] は行番号、二つ目の [0] は列番号を示しています。
こういったプログラムの配列は、要素番号が 1 ではなく 0 からはじまっているので、1行目の 1列目なのに [0][0] はセル A1 を示していることに注意してください。

14 ~ 15行目 デフォルトのカレンダーを取得する

  // デフォルトのカレンダーを取得する
 let Calendar = CalendarApp.getDefaultCalendar();

15行目では、Google カレンダーを扱うためのクラス「Class CalendarApp」で用意されている関数「getDefaultCalendar()」を使って、このスクリプトを実行している Google アカウントのデフォルトカレンダーを、変数 Calendar  に取得しています。
デフォルトカレンダーは、その Google アカウントのメールアドレスと同じ ID が設定されたカレンダーのことです。

16 ~ 17行目 カレンダーのタイムゾーンの設定

  // カレンダーのタイムゾーンの設定
  Calendar.setTimeZone("Asia/Tokyo");

17行目では、変数 Calendar  に取得したデフォルトカレンダーのタイムゾーンを、setTimeZone(timeZone)  によって日本時間("Asia/Tokyo")に設定しています。
このタイムゾーンの設定を行っていなかった場合、違うタイムゾーンの時間でイベント・予定が作成される可能性があるためです。
このような日時を扱うプログラムを作成する場合には、以下の記事で説明している「プロジェクトの設定」にあるタイムゾーンも確認しておきましょう。

18 ~ 20行目 日程情報を検索していく処理

  // 日程情報を検索していく処理
  for(let i = 1 ; i < SheetData.length ; i++){
    for(let j = 2 ; j < SheetData[i].length ; j+=3){

ここからは、シート内に設定されている内容をチェックしていきます。
19行目では行方向(縦)、20行目では列方向(横)にくり返すためのループ(くり返し)が設定されています。19行目の { は 28行目、20行目の { は 27行目の } と対応しています。

19行目を例にすると、for (  ){ のカッコ内には ; で区切って、3つの要素が書かれています。

  1. let i = 1
    一つ目の要素は、ループをはじめる際に行われる処理が書かれています。一般に、ループの回数を制御するためのループカウンタの初期値を設定されることが多いです。今回も縦方向の処理を制御するループカウンタである i を宣言し、初期値として 1 を設定しています。 ※実際には、2行目から処理を行うが、配列の要素としては 1 が設定されている。

  2. i < SheetData.length
    二つ目の要素には、ループが継続する条件が書かれています。
    右辺の SheetData.length は二次元配列である SheetData に .length を付けることで、SheetData の要素数を表します。
    この条件を満たしている間、19行目の行末の { と 28行目の } の間をループします。

  3. i++
    三つ目の要素は、一回のループが終わったタイミングで行われる処理が書かれています。
    一般にループカウンタを加算/減算する処理が書かれていることが多いです。今回も、ループカウンタを加算する処理が書かれています。
    この ++ という表現は、プログラム特有の表現ですが、i の中身を 1つ増加させるインクリメントと呼ばれる処理です。i-- と書かれていた場合には、1つ減算するデクリメント(減算)となります。

19行目要素数と、ループカウンタの i を比較することで、縦方向にすべての要素をチェックすることになります。

20行目では、横方向のチェックを行うループを設定しています。19行目と同様に、for (  ){ のカッコ内には、次のように書かれています。

  1. let j = 2
    チェックを行うのは C 列からなので 2 が設定されている。

  2. j < SheetData[i].length
    SheetData[i].length は、横方向の配列要素数です。

  3. j+=3
    この +=3 という表現は、j = j + 3 を省略して表記したもので、j が 3 加算されるものです。

21行目 予定が入力されているかの判断

      if(SheetData[i][j] != ""){

この行では、if(  ) のカッコ内にある左辺: SheetData[i][j]、すなわりループカウンタの ij によって指定されるセルの内容が、右辺:空("")であるかをチェックしています。
記号として != が使われているので、右辺と左辺が等しくない(≠)場合に、行末の { から 26行目の } までが実行されます。

22 ~ 25行目 予定があった場合に、開始時間を参照して予定を作成する

        // 予定があった場合に、開始時間を参照して予定を作成する
        Calendar.createEvent(SheetData[i][j],
                              new Date(TargetYear+"/"+ActiveMonth+"/"+SheetData[i][0]+" "+SheetData[i][j+1]),
                              new Date(TargetYear+"/"+ActiveMonth+"/"+SheetData[i][0]+" "+SheetData[i][j+2]));

23 ~ 25行目では、セルに入力された内容でイベント(予定)を作成しています。イベントの作成には、Google カレンダーを扱うためのクラス「Class CalendarApp」で用意されている関数「createEvent(title, startTime, endTime)」を使用しています。

  1. SheetData[i][j]
    イベントのタイトルとなる文字列を指定しています。

  2. new Date(TargetYear+"/"+ActiveMonth+"/"+SheetData[i][0]+" "+SheetData[i][j+1])
    イベントの開始時刻を指定しています。

  3. new Date(TargetYear+"/"+ActiveMonth+"/"+SheetData[i][0]+" "+SheetData[i][j+2])
    イベントの終了時刻を指定しています。

new Date(  ) の中には、開始時刻と終了時刻は、2022/02/14 12:34 という文字列になるように、変数やセルの内容を連結しています。SheetData[i][j] がタイトルが指定されているセルで、開始時刻が指定されているセルがその右隣なので SheetData[i][j+1]、更に右隣の終了時刻が SheetData[i][j+2]、となっています。

この 23 ~ 25行は一つの命令ですが、1行が長くなっているので、複数行に分けて書かれています。複数行に分かれているものの、一つの命令なので 23行目・24行目の行末には ; がなく、25行目にだけ ; が書かれています。

26行目 if の終わり

      }

26行目は、22行目の if に対応するブロックの終わりの示す } です。

27 ~ 28行目 ループの終わり

    }
  }

27行目は 20行目、28行目は 19行目に対応する、ループの終わりを示す } です。

29 ~ 30行 処理完了表示

  // 処理完了表示
  Browser.msgBox("日程をカレンダーに出力しました。");

30行目は、処理が終了したことを示すメッセージボックスを表示しています。
Browser.msgBox( ) で、引数に与えられた文字列によって下図のようにメッセージボックスを表示しています。

メッセージボックスの表示

これは、Google スプレッドシート内で動作するスクリプトで使用できる「Class Browser」によるもので、今回は msgBox(prompt) を使用していますが、inputBox(prompt) を利用すればユーザーに入力を求めることも可能です。

31行目 関数の終了

}

31行目は、5行目の { に対応している } です。



この記事が気に入ったらサポートをしてみませんか?