Googleスプレッドシート GASを使って、あしたへジャンプ! (GASで日付を扱うポイント他)
前回の XMATCH 超応用例 から派生した小ネタです。
ちなみに「あしたへジャンプ」は 1990年代前半に NHKの教育テレビで放送されていた 小学校高学年向けの道徳ドラマです。
ただ単に 今日以降のセルにジャンプするという動作のお題から、思い出した(思いついた)だけなので、今回の記事とは一切関係ありません。そして年代がバレるww
前回の平日更新記事
スプレッドシートで 直近の予定の日付に飛びたい
前回は関数での解決策を検証した、こちらのあるあるなお題。
今回は、GAS(GoogleAppsScript)で シートを開いた時、もしくはボタンを押したら 直近の予定の行(セル)へ飛べるようにしたい!
を解決する GASを検証したいと思います。
「今日の日付へ飛びたい」は GASをお勧めしない
ただ、前回も書いた通り 本来この処理は GASはお勧めしません!
GAS(Javascript)は日付の扱いがちょっと面倒
スマホ(アプリ版)で利用できない
GAS の activateは 省エネな動き
理由としては この3つをあげました。
唯一解決できない スマホ利用問題は除外するとして、今回はあえて「お勧めしない」GASに手をだしてみましょう。
女子に「先生~、〇〇くんが GASを使おうとしています」って言われそうで、まさに道徳番組でありそうな展開ですねw
GASを実行して A列の直近の予定の日付にジャンプしたい
前提条件としては以下の通り
上の画像の動きは 最後の要件 「ジャンプ先のセルが一番上に表示されるようにしたい」が満たせてませんが、まずはこれを作っていきましょう。
今回はハマりやすいポイントも抑えていきます!
GASで日付を扱う
早速コードを書いてもいいんですが、せっかくなんで 面倒なGASにおける日付の扱いを少し解説しておきましょう。
GASでの日付の扱いは、基本的には Javascriptと一緒ですが、一部 GASならではのメソッドもあります。
new Date() は シート関数でいうところの NOW()
今日の日付を得る為には
new Date()
と記述します。
中身をログに出力して確認すると
このように 、この関数が実行された 日付けと時間が返っているのがわかります。暦の表記が英語仕様でわかりづらいですが、
2023年2月26日 日曜日 14:29:59
となってます。
つまり、ここで得られる 日付は今日という日付ではなく、〇時〇分〇秒という情報も含む 今の日時 ってことです。(ただしくは 日付オブジェクトです)
シート関数でいうと、TODAY() ではなく NOW() と考えると わかりやすいかも。
もし、ここでログに出力された時間が ズレている、もしくは 「Japan Standard Time」となっていない場合は、このプロジェクト(GAS)のタイムゾーン設定が日本以外になっている可能性があります。
プロジェクトの設定(歯車アイコン)から、タイムゾーンを確認して、日本時間になっていない場合は修正しましょう。
Utilties.formatDate で日付の表示形式を整える
これは Javascriptにはなくて GASオリジナル です。
GASには日付オブジェクトを文字列に変換する専用のユーティリティメソッドが用意されてます。
これはシート関数だと (日時専用の)TEXT関数みたいな感じですかね。
日付を自分の欲しい表示形式にできます。
今回、日付のみをスラッシュ区切りで取得するために
Utilities.formatDate(date,"Asia/Tokyo","yyyy/MM/dd");
このようにしましたが、結果として得られる 2023/02/26 は文字列 Stringという扱いです。今日の日付の Dateオブジェクトではありません。
今日の 「日付」だけが欲しい時
今回は、後の処理で使う用に Dateオブジェクトが欲しいのですが、
new Date() ・・・現在時刻を含む 今を表す 日付オブジェクト
Utilties.formatDate 後 ・・・ 文字列
となると、どうすればいいのか?
シート関数ならシリアル値という考え方があるので INT関数で小数部分カットという技が使えるんですがw
実はUtilities.formatDateで 日付 のみにした文字列を 再度 new Date() に与えることで 本日の日付のみの Dateオブジェクトを生成できます。
function myFunction() {
let date = Utilities.formatDate(new Date(),"Asia/Tokyo","yyyy/MM/dd");
const today = new Date(date);
console.log(today);
}
これは、Utilites.formatDateが使える GASならではの書き方で、Javascriptの場合は、
let today = new Date();
today = new Date(today.getFullYear(),today.getMonth(),today.getDate());
こんな書き方をすることが多いかと思います。
上のように new Date() は 日付っぽい文字列をカッコ内に入れる以外に、カンマ区切りで 年、月、日 を数値で入れて 日付オブジェクトを生成できます。
これは、シート関数の DATE(年,月,日) っぽいですね。
ただし、直接 new Date() に数値を入れる場合は注意点があります。
ここも月が注意ポイントなんですが、
このように 2023年2月27日 に 本日の日付オブジェクトから
today.getMonth()
で月の数値を得ると 2月なのに 1と表示されます。
逆に 2023年2月27日 の日付オブジェクトを得ようと
new Date(2023,2,27)
とすると、Mar(マーチ)つまり、 2023年3月27日 となってしまいます。
これは Javascript の 月の数値 は 0から11までと、日本人的感覚からすると-1ズレているからです。
0が January ・・・1月
11が December ・・・ 12月
うーん、わかりづらい。
とりあえず、本日の日付のみの Dateオブジェクトが作れました。
getValue(getValues)で取得した セルの日付は Dateオブジェクトになってる
それでは スプレッドシートのセルに入っている 日付データは、同じように getValueで取得した後、 new Date() に渡して日付オブジェクトにすればよいのか?
実はそんな必要はなく、セルの日付はそのままで Dateオブジェクトになっています。
このように 「今日の日付」の方の型は strig、2023/2/26 の方の型は object となっているのがわかります。
要は取得してそのまんま日付オブジェクトとして使えるってことです。
余談:セルの日付を文字列として使いたい場合は getDisplayValueが便利
余談ですが、取得した日付を Gmailの本文に使ったり、他の文字列と連結させたり、文字列として比較して一致を判定したい場合は、getValueで Dateオブジェクトになってしまうと不便だったりします。
そんな時は getDisplayValue(getDisplayValues) で取得すると便利です。
getDisplayValueは シート上で 表示形式を反映させた 表示そのままを 文字列として取得できるメソッドです。
特に日付を 文字列として取得したい時は便利です。
日付オブジェクトは 一致は判定できないが 大小比較はできる
function myFunction() {
const sheet = SpreadsheetApp.getActiveSheet();
const values = sheet.getRange(1,1,1,2).getValues();
console.log(values[0][0] == values[0][1]); //false
console.log(values[0][0] <= values[0][1]); //true
console.log(values[0][0] >= values[0][1]); //true
}
同じ日付を表す 日付オブジェクト同士を 等価演算子 == で一致するか判定しようとしても false となってしまいます。つまり日付オブジェクトは、そのままでは 一致を判定できないってことです。
日付同士の一致を判定したい場合は、Utilites.formatDate で文字列にしたり、最初から getDisplayValueで取得し 文字列同士で判定するか、getTime()などのメソッドで ミリ秒(数値)にして 判定する必要があります。
一方 < 、 <= 、> 、 >= といった比較演算子は使えます。意外かもしれませんが、日付オブジェクトのまま 大小の比較判定は 出来るってことです。
わざわざ比較する際に ミリ秒に変換する必要はありません。
今回のお題が 「本日以降の直近の日付」なので、比較演算子による処理が使えそうですよね?
これでGASにおける「日付」の基本理解と準備は整いました。
Javascript における XMATCHの上位互換 検索メソッド findIndex
Googleスプレッドシートの 最強関数の一つ FILTER関数は、なぜ検索・抽出において 圧倒的に便利なのか?
もちろん配列処理し、複数の結果を配列で返せるという点も魅力ですが、検索条件に 式が使えるという点も大きいんじゃないでしょうか?
FILTER関数は 条件式が TRUEを返した要素を抽出するという動きをします。
XLOOKUPやXMATCHは、ワイルドカードや近似値一致など単純一致以外の検索もできる 便利な検索関数ではありますが、
もし、XLOOKUPや XMATCH の一致条件が 条件式で書けたら?
って思うことがあります。
これできたら XLOOUP、XMATCHは 真の最強検索関数なんだけどなー。
実は GAS(Javascript)の配列メソッドには、このXMATCH の一致条件が条件式で書けたら? という、あんなこといいな、出来たらいいなを実現する ドラえもんの秘密道具のような強力な検索メソッドがあります。
それが findIndex() です。
説明をみると
とあります。まさに今回の「本日以降の直近の日付」の位置(行番号)を得るのに最適なメソッドですね。
function sample(){
const array = [
["田中",23],
["山田",26],
["鈴木",28],
["木内",30],
["鮫島",33],
["宮内",37],
["三田",42]
]; //6人の名前と年齢
//31歳以上を探す
const index = array.findIndex(v => v[1] >= 31);
console.log(index); //4 ・・・ 0スタートなので 5番目の鮫島
}
Googleスプレッドシートで使えそうな サンプルコードはこんな感じ。
スプレッドシートからgetValuesで取得したデータを想定して、名前と年齢が 入った 二次元配列で 31歳以上の年齢 という条件を満たす一人目の位置を取得しています。
二次元配列なので 各要素 v (配列)の 2つ目 (0始まりなので v[1])を取り出して比較しています。
そして結果は 0始まりの配列の indexなので、 4ということは人間目線では +1して 5番目であるという点も注意です。
Array.prototype (配列メソッド)は、アロー関数でシンプルに書けるとクールです。何度も使って慣れましょう。
日付の扱いを踏まえ、findIndexを使って 「直近の予定の日付に飛ぶ」コードを書いてみましょう。
A列の 直近の予定の日付にジャンプするコード
function toNextSchedule() {
const sheet = SpreadsheetApp.getActiveSheet(); //開いているシート
const date = Utilities.formatDate(new Date(),"Asia/Tokyo","yyyy/MM/dd");
const today = new Date(date); //今日の日付
const range = sheet.getRange("A:A");
const days = range.getValues();
const index = days.findIndex(day => day[0] >= today);
sheet.getRange(index+1,1).activate(); //行番号なので配列の位置に +1
}
上で 日付の扱いと findIndex には触れたので、解説は不要ですよね? 面倒なんで A列を値を全部ぶっこ抜きしてます。
お目当てのセルにジャンプする処理は、最後の
sheet.getRange(index+1,1).activate()
ここです。activate メソッドを使います。
思ったよりは シンプルなコードじゃないでしょうか?
GASの動きを確認
ボタンに割りあてて動かしてみましょう。
ボタンを作成し、GASの関数を割り当てる流れは スピンボタンの回で丁寧めに書いています。
今日 2/26の日付が A列にはないので 直近の日付である2/27 がアクティブになりましたね。
ただし、前回も触れましたが GASの省エネな動き、アクティブなセルが画面の一番下で終了してしまう問題 が残っています。
コードを加筆して、これを解決しましょう!
スプレッドシートで直近の予定の日付に飛びたい(アクティブセルが左上に表示される状態にしたい)
この問題を解決するに、上に 16行表示されてるから 本来の行番号 + 16して、16行下をアクティブにすれば・・・なんてことを考える人がいますが、完全に見誤ってます。
Gmailで ワードの 送り状みたいな 日付を右に寄せたり、レイアウトに凝りすぎた本文を スペースキーで作って、相手の環境で見たらガタガタってパターンと一緒です。
そもそも 直近じゃない日がアクティブになったら意味ないですし、パソコンのモニターやその人が使ってる表示倍率によって 1画面に何行表示されるかは変わってきます。
じゃあどうするか?
上から下へという動きで アクティブセルが見つかった(画面に入った)段階で止まるので一番下に表示されるなら、表の一番下から 上へという動きにすれば、アクティブセルが見つかった(画面に入った)段階で止まると 一番上に表示されるのでは?
って考えるとよさげじゃないでしょうか?
この動きは GASを使わずに検証できます。やってみましょう!
下から上へ activate をテストしてみる
GAS実行前に A1アクティブ状態から手動で Ctrl + ↓ を何度か押して、一番したの A1000セルをアクティブにし 表示させた状態とします。
この状態で GASを実行すると、予想通り 見つかった セル (今回の場合は 2/27)が左上の状態で アクティブとなりました。
このGAS実行前の準備を コードに追記すればいけそうですね!
シートの最終行を取得する
では、GASでアクティブなシートの最終行を取得するにはどうすればよいか?
最終行と言えば、 getLastRow()というメソッドがありますが。
でも、実はこれ sheet(シート) に対して使う時と、range(セル範囲)に対して使う時で挙動が違います。
シートに対して getLastRowを使った場合は、そのシートのデータが存在する最終行の行番号、つまり getDataRangeの最終行番号を返します。
一方、sheet.getRange でシートからセル範囲を取得した場合は、そのセル範囲の最終行の行番号を返します。
A:Aとした場合はシートの最終行(今回の場合は1000)となりますし、A10:A100とすれば最終行は100となります。
今回は シートの最終行をまずはアクティブにしたいので、列指定したrangeに対して getLastRow() で良さそうです。
そこまでの処理を反映させる魔法の言葉 flush
flash(閃光のフラッシュ) じゃなくて flush(あふれ出る)という意味の方の単語です。
シートの最終行を1度アクティブにしてから、ヒットした 今日以降の直近の日付をアクティブにする「下から 上」の動きをさせる為に、
このように12行目に
sheet.getRange(range.getLastRow(),1).activate();
としただけではうまくいきません。
あれれーおかしいぞー?、じゃあちょっと待てばよいのかな?と、
Utilities.sleep() というミリ秒 処理を待たせる方法を使ってもダメです。
GASは処理負荷を軽減する為か、コードの処理が全て終わってからでないと画面上反映されない という特徴があります。
これを知らないと、ここでかなりハマります。
例えば GASで 数式を入れて その計算結果を getValue() しようとしても、
計算結果が取得出来なかったり、setValue()で色々入力してから スプレッドシートをPDF化したはずなのに、入れたはずの値が入ってなかったり。
こんなお悩みは、ほぼ全て SpreadsheetApp.flush() で解決します。
SpreadsheetApp.flush()
と書いて使います。
これを入れることで、「ここまでの処理を反映させる」ことができます。今回の場合は、最終行をアクティブにした状態を反映させて、その後 今日以降の直帰の日付に飛べば 良いですね!
最後に 完成版のコードと 検証です。
A列の 直近の予定の日付にジャンプし (上部)に表示するコード
function toNextSchedule() {
const sheet = SpreadsheetApp.getActiveSheet(); //開いているシート
const date = Utilities.formatDate(new Date(),"Asia/Tokyo","yyyy/MM/dd");
const today = new Date(date); //今日の日付
const range = sheet.getRange("A:A");
const days = range.getValues();
const index = days.findIndex(day => day[0] >= today);
sheet.getRange(range.getLastRow(),1).activate(); //一度最終行をアクティブに
SpreadsheetApp.flush(); //画面更新
sheet.getRange(index+1,1).activate(); //行番号なので配列の位置に +1
}
1度 一番下の行を表示してから、直近の日付がアクティブになる動きが 目で追えるので、なんかパッパッといった2段階の動きに若干違和感はあるものの、とりあえず 完成しました。
もちろん この関数名を onOpen() にすれば、開いた時に直帰の日付がアクティブ(左上)になるシートとなります。
次回は また関数の単発ネタを
前回、XMATCH関数 の近似値一致 大 と HYPERLINK関数で 実装した
「A列の今日以降の直帰の予定に飛ぶ」
という処理を 今回は GASで実装してみました。
どうでしょうか、別にGAS使わなくてもよくない?って思いませんか?
個人的には 関数・数式、標準機能で出来ることは、そっちで処理して GASは最後の手段って考えなんで、これは自分では使わないかなー。
でも、GASでの日付の扱い、使い方によっては便利な getDisplayValue、結構好きな findIndex、処理を反映させる flush と、この辺りを説明するのに良い題材だったと思います。
次回の平日更新は、また関数小ネタを予定。
この記事が気に入ったらサポートをしてみませんか?