見出し画像

Google広告にてGoogleカレンダーAPIを使い祝日のオン・オフを自動化する方法

今日は楽しいAPIの時間(なんだいきなり)。

Google広告やYahoo!広告で曜日、あるいは特定の日に配信をオン(オフ)にしたいときがあると思います。

例えば、土日は配信したくない、という案件であれば管理画面のデフォルトのスケジュール機能を使えば自動でオンオフが可能です。

ですが、加えて「祝日」も停止したい。。
という場合はどうでしょうか?

おそらく、手動で頑張ってオンオフしたり、その日をピンポイントで停止するような自動ルールを設定する、というような対応になりがちだと思います。

今回は、GoogleカレンダーAPIを使用して、祝日のオンオフも自動化しようぜ!!っていうハッピーな話です。

なお、Google広告は、「Google Ads Script」を使用して、
Yahoo!広告は、「Yahoo!広告 API」+「Google Apps Script(GAS)」を利用します。
※GASじゃなくても「APIを叩ける」かつ「定期実行が可能」であればできます。が、GASを使うとGoogle カレンダー APIとの連携がサクッとできます。

今回はGoogle広告の話をして、次回Yahoo!広告の話をいたします。

ではいきましょう。

Google カレンダーAPIとは

正式には、「Google Calendar API」ですが、個人的に日本語の方が分かりやすい気がするので、この記事では「Google カレンダー API」と書いています。

名前からお察しの通り、
APIと通してGoogle カレンダーの操作や処理ができます。

  • カレンダーへのイベント追加・削除

  • カレンダー内のイベント取得・閲覧

  • イベントのリマインド

  • 定期イベントの設定

  • etc・・・

といったように色々できます。

イベントの追加、などがあるように、もちろん自身のカレンダーにイベントを作成したりとかもできますが、今回は祝日を取得したいだけですので、
自分のカレンダー云々ではなく、Googleが用意している日本の祝日カレンダーを使わせてもらいます。(詳しくは後述)

Google広告での話

まず、Google広告での話です。
Google広告では「Google Ads Script」を用いましょう。
停止用と再開用の2つ作っていきます。

翌日が祝日の場合に停止

アカウントに入り、「ツールと設定」→「スクリプト」をクリックします。

新しく作成するので、「+」ボタンを押します。

「新規スクリプト」をクリック。

スクリプト名を入力します。名前は何でもOK。

次に右上の方にある「API(上級)」をクリックしましょう。

今回、Calendarを使うので、チェックを入れて保存します。

これで準備は整ったのであとはコードを書くだけです。

ちなみに、一番最初にスクリプトを動かすとき(保存するときやプレビューするとき)には「承認」を求められますが、問題ないので承認しましょう。

最初にコードを記載しますね。
詳細は後から説明していきます。

function main() {
  
  var calendarId = "ja.japanese#holiday@group.v.calendar.google.com";
  
  function format(day){
  const y = day.getFullYear();
  const _m = day.getMonth() + 1;
  const m = ('00' + _m).slice(-2);
  const _d = day.getDate();
  const d = ('00' + _d).slice(-2);
   
  return y + '-' + m + '-' + d;
  }
  
  var nowTime = new Date(); // 日本時間とは限らない
  var tomorrowTime = new Date((new Date()).setDate(nowTime.getDate() +1 )); // 日本時間とは限らない
  var tomorrowTimePlus = new Date(new Date((new Date()).setDate(nowTime.getDate() +1 )).setMinutes(nowTime.getMinutes()+5)); // 日本時間とは限らない
  
  var timeMin = tomorrowTime.toISOString();
  var timeMax = tomorrowTimePlus.toISOString();
 
  
  var calendarEvents = Calendar.Events.list(calendarId, {
    timeMin: timeMin,
    timeMax:timeMax,    
    singleEvents: true,
    orderBy: 'startTime'
  });
 
    
  var event = calendarEvents.items[0] ?? {start:{date:null}};
  
  var now = new Date(Date.now() +  (new Date().getTimezoneOffset() + 9 * 60) * 60 * 1000);
  var tomorrow = new Date(now.setDate(now.getDate()+1));
  
if(format(tomorrow) === event.start.date){
  console.log('停止');  
  var campaigns =  AdsApp.campaigns().withIds([```キャンペーンIDの配列```]).get();

  while(campaigns.hasNext()){
 var campaign = campaigns.next();
    campaign.pause();
}
}
}

はい、コードとしてはこれだけです。

簡単にいうと、『翌日が祝日なら停止してね』というのを書いているだけです。

このコード自体は定期実行の設定ができます(後述)ので、
例えば、毎日23時ころにこのコードを実行すれば、「翌日が祝日の場合」はその段階で停止されるわけです。

もちろん翌日が祝日でなければ、特に何もしません。

これは祝日が連続していても問題は起こりません。
仮に10日と11日が祝日の場合を考えます。

9日の23時に判定⇒10日が祝日なので停止スクリプトが動く⇒停止
10日の23時に判定⇒11日が祝日なので停止スクリプトが動く⇒すでに停止されているので引き続き停止

祝日が連続する際に考慮しなくてはならないのは「再開」の場合ですが、
その話は後ほどします。

ではコードを見ていきましょう。

まずこちらは「日本の祝日カレンダー」を取ってきています。

var calendarId = "ja.japanese#holiday@group.v.calendar.google.com";

以下の関数はJavaScriptのDateインスタンスを整形して
「2022-12-12」という形の文字列にするための関数です。

人によってはこういう関数は最後に定義したいという方もいると思いますが、もちろんそれでも構いません。

  function format(day){
  const y = day.getFullYear();
  const _m = day.getMonth() + 1;
  const m = ('00' + _m).slice(-2);
  const _d = day.getDate();
  const d = ('00' + _d).slice(-2);
   
  return y + '-' + m + '-' + d;
  }

次の記述は少しややこしいのですが、nowTimeは現在時刻です。ただし、日本時間とは限りません。

私もすべてのアカウントみてはいないので、アカウントごとに異なるのか、Google Adsで共通なのかも不明ですが、
私が見た限り、夏時間は「GMT -07:00」で冬時間は「GMT -08:00」になりました。

日本時間でないこと自体は特に問題ありません。
どちらにせよ、のちほどイベントを検索する際にはUTCに変更して検索します。

var nowTime = new Date(); // 日本時間とは限らない
var tomorrowTime = new Date((new Date()).setDate(nowTime.getDate() +1 )); // 日本時間とは限らない
var tomorrowTimePlus = new Date(new Date((new Date()).setDate(nowTime.getDate() +1 )).setMinutes(nowTime.getMinutes()+5)); // 日本時間とは限らない

tomorrowTimeが現在時刻に「+1日」した時刻、
tomorrowTimePlusが現在時刻に「+1日5分」した時刻です。

あとでイベントを検索するときに「同時刻」で検索すると何も検出できないため、5分だけずらしています。
別に5分じゃなくても1分とかでも問題ないです。

そして、以下のコードで
イベントを検索する際の範囲のMinimumとMaximumの変数を作ります。

単純に、上記のtomorrowTimeとtomorrowTimePlusをUTC時刻のISO8601形式に変換しています。

  var timeMin = tomorrowTime.toISOString();
  var timeMax = tomorrowTimePlus.toISOString();

これらを用いて、イベントを検索するコードが次です。

  var calendarEvents = Calendar.Events.list(calendarId, {
    timeMin: timeMin,
    timeMax: timeMax,    
    singleEvents: true,
    orderBy: 'startTime'
  });

イベントを検索してきて、「calendarEvents」という変数に入れているわけですね。

次に、calendarEvents自体はいらない情報も入っているので、その中から「items」を取り出しています。

配列で返ってくるのでitems[0]としています。

翌日が祝日出ない場合は『空の配列』になるため、items[0]がundefinedになります。その際は「{start: {date: null}}」を返すようにしています。
※「??」はnull合体演算子と呼ばれるものです。Google Ads Script特有のものではなく、JavaScriptの記法。

  var event = calendarEvents.items[0] ?? {start:{date:null}};

以下のコードは、『翌日の日本時間の日付』を取得しているものです。

  var now = new Date(Date.now() +  (new Date().getTimezoneOffset() + 9 * 60) * 60 * 1000);

  var tomorrow = new Date(now.setDate(now.getDate()+1));

「getTimezoneOffset()」は、ローカルの時刻からUTCまでのタイムゾーンの差を分単位で返すメソッドです。起点はローカルの時刻なので注意です。

例えばローカルが「GMT -08:00」の場合は、ローカルから見るとGMT(つまりUTC)は +8時間なので、+480(min)が戻り値です。

日本ならローカルが「GMT +09:00」ですので、GMT(UTC)は日本からみると -9時間となり、-540(min)が戻り値です。

要するに、new Date().getTimezoneOffset() で ローカルとUTCとの差分を出しています。
さらに + 9 * 60 することでローカルと日本時間との差分を計算しています。

そして、ローカルのUNIX Time(Date.now())に、上記の差分のミリ秒(*60 * 1000)を加えます。

で、これを new Date()の引数に入れることで、Dateインスタンスの形式で返ってきます。

コンソールで表示すると、(GTM -08:00)などの表記はあると思いますが、時刻は日本時刻になっているはずです。

そこから日付に +1 して tomorrowを計算しています。

そして、いよいよ最後のコードです。

if(format(tomorrow) === event.start.date){
  console.log('停止');  
  var campaigns =  AdsApp.campaigns().withIds([```キャンペーンIDの配列```]).get();

  while(campaigns.hasNext()){
 var campaign = campaigns.next();
    campaign.pause();
}
}

まず、「format(tomorrow)」のところです。
↓↓formatは最初に定義した関数ですね。↓↓

  function format(day){
  const y = day.getFullYear();
  const _m = day.getMonth() + 1;
  const m = ('00' + _m).slice(-2);
  const _d = day.getDate();
  const d = ('00' + _d).slice(-2);
   
  return y + '-' + m + '-' + d;
  }

Dateのインスタンスを入れると、「2022-12-12」などの形式の文字列を返す関数です。

そして、「event.start.date」について。
「event」は↓↓で定義した変数ですね。

 var event = calendarEvents.items[0] ?? {start:{date:null}}; 

この「event」の中身は以下のような感じになっています。

なので、event.start.dateに「2022-08-30」という文字列が入ります。

(もちろん、eventには祝日の場合は値が入りますが、祝日でない場合は、?? より右側の {start: {date: null}}になります。)

if(format(tomorrow) === event.start.date)

つまり、↑↑は
『明日の日付』が『祝日の日付』と一致しているか??をみます。
翌日が祝日でない場合はevet.start.dateは「null」になるので、ifの中身はfalseになります。

そして、翌日が祝日の場合は if(ture)になるため、
以下のコードが実行されます。

  var campaigns =  AdsApp.campaigns().withIds([```キャンペーンIDの配列```]).get();

  while(campaigns.hasNext()){
 var campaign = campaigns.next();
    campaign.pause();
}

まず、停止すべきキャンペーンのIDを取得しています。

AdsApp.campaigns().withIds([```キャンペーンIDの配列```]).get();

withIds()で渡す引数は配列になります。
止めたいキャンペーンIDが「100,200,300」なら、
withIds([100, 200, 300])とう具合にセットします。

あとは while文でキャンペーンをループさせて、
それぞれ停止させていくわけですね。

これが『翌日が祝日の場合に停止』の概要になります。

コードを書き終わったら保存します。

最後に「頻度」を設定します。

毎日 22:00~23:00 の間で実行、が安牌でいいと思います。
23:00~00:00だと万が一のことも考えられるので、22:00~23:00がおすすめです。

では再開の場合を見ていきましょう。

昨日が祝日で当日が祝日以外の場合に再開

再開の場合も基本的には流れは同じです。

ツールと設定 > スクリプト
から新規でスクリプトを作成します。

スクリプト名を設定します。

API(上級)でCalendarを有効にするのも同様です。

全体のコードは下記になります。

function main() {
  
  var calendarId = "ja.japanese#holiday@group.v.calendar.google.com";
  
  function format(day){
  const y = day.getFullYear();
  const _m = day.getMonth() + 1;
  const m = ('00' + _m).slice(-2);
  const d = day.getDate();
   
  return y + '-' + m + '-' + d;
  }
  
  var nowTime = new Date(); 
  var yesterdayTime = new Date((new Date()).setDate(nowTime.getDate() - 1));  
  var todayTime = new Date((new Date()).setDate(nowTime.getDate()));  
  
  
  var timeMin = yesterdayTime.toISOString(); //UTC
  var timeMax = todayTime.toISOString();  //UTC
  
  //↓検索はUTCで実施している
  
  var calendarEvents = Calendar.Events.list(calendarId, {
    timeMin: timeMin,
    timeMax: timeMax,
    singleEvents: true,
    orderBy: 'startTime'
  });
  
  //↓↓検索はUTCだが取得したものに対する[start.date]の日時は日本の日付
  
  var event0 = calendarEvents.items[0] ?? {start: {date: null}};
  var event1 = calendarEvents.items[1] ?? {start: {date: null}};
 
 
  var now = new Date(Date.now() +  (new Date().getTimezoneOffset() + 9 * 60) * 60 * 1000);

  var today = new Date(now.setDate(now.getDate()));
  var yesterday = new Date(now.setDate(now.getDate()-1));
  
 
  
if((format(yesterday) === event0.start.date) && (format(today) !== event1.start.date)){
  console.log('再開');
  
  var campaigns =  AdsApp.campaigns().withIds([921369064]).get();
while(campaigns.hasNext()){
 var campaign = campaigns.next();
    Logger.log(campaign.getName());
    campaign.enable();
}
}
}

ほとんど似たようなものです。
停止のときは「翌日」を見ていますが、
再開の場合は「昨日と当日」を見ています。

『昨日が祝日で当日が祝日以外の場合』配信を再開す
ようにしています。

『昨日が祝日』だけの条件ですと、
当日も祝日(2連休)の場合にも再開してしまうので、
当日の条件も入っています。

なお、土日も停止したいという場合は、
管理画面の広告スケジュール側で設定しておいてください
祝日カレンダーでは土日は判別していませんので。

例えば、金曜日が祝日で、土曜日が祝日ではない場合、
上記のコードが実行されれば、土曜日の時点で「キャンペーンのステータスはオン」になります。

ただし、広告スケジュールで土日に配信しない設定をしていれば、そもそも土曜日には配信されません。
(キャンペーンがオンでもスケジュールで除外していれば、配信されない)

コードについては、停止の場合とほぼ同様ですので、かいつまんで説明したいと思います。

まずはこちらです!

  var event0 = calendarEvents.items[0] ?? {start: {date: null}};
  var event1 = calendarEvents.items[1] ?? {start: {date: null}};

停止の場合は「翌日1日分」だけイベントを取得していました。(祝日でなければitemsは空の配列)

今回は、「昨日と当日の2日分」イベント検索しています。

①昨日も当日も祝日

items[0]もitems[1]もイベントが入るので、
「event0もevent1」も値が入ります。

つまり、event0.start.dateやevent1.start.dateが
その祝日の日付が入ります。(2022-12-12のような形の文字列)

②昨日が祝日で当日は祝日ではない

event0の方にだけ値が入ります(昨日の祝日イベント)
event1はundefinedになり、??(null合体演算子)が動き
{start: {date: null}}がセットされます。

③昨日が祝日ではないが当日が祝日

この場合もeventのitemsには1つ分のイベントしか入らないため、
event0 の方にだけ値が入ります(当日の祝日イベント)。
event1は{start: {date: null}}がセットされますね。

④昨日も当日も祝日ではない

itemsは空なのでevent0もevent1も{start: {date: null}}になります。

そして、以下ではtodayとyesterdayには日本時間での時刻になっおり、
format()関数を通すことで、日本時間における『昨日の日付』と『当日の日付』を返します。

  var now = new Date(Date.now() +  (new Date().getTimezoneOffset() + 9 * 60) * 60 * 1000);

  var today = new Date(now.setDate(now.getDate()));
  var yesterday = new Date(now.setDate(now.getDate()-1));

さて、if文の中身が厄介ですが見ていきましょう。

if((format(yesterday) === event0.start.date) && (format(today) !== event1.start.date))

まず、状況を上記の①~④に合わせて見てきます。

①昨日も当日も祝日
items[0]もitems[1]もイベントが入るので、
「event0もevent1」も値が入ります。

昨日:2023年5月4日(みどりの日)
当日:2023年5月5日(こどもの日)

としましょう。

format(yesterday)は "2023-05-04"になり、
format(today)は "2023-05-05"になります。

event0.start.dateは "2023-05-04"であり、
event1.start.dateは "2023-05-05"です。

よって、
format(yesterday) === event0.start.date はtrueです。
format(today) !== event1.start.date は falseです。

後者は「!==」なので、等しくないときに trueで
等しい場合は false であることに注意ください。

よって、この場合 if文全体で falseになるため、実行されません。

当日も祝日なら、配信再開したくないので、
再開しないでOKですよね。

②昨日が祝日で当日は祝日ではない

event0の方にだけ値が入ります(昨日の祝日イベント)
event1はundefinedになり、??(null合体演算子)が動き
{start: {date: null}}がセットされます。

昨日:2023年5月5日(こども日)
当日:2023年5月6日(祝日でない)

とします。

format(yesterday)は "2023-05-05"になり、
format(today)は "2023-05-06"になります。

event0.start.dateは "2023-05-05" になります。

event1はundefinedなので、{start: {date: null}}がセットされるため、
event1.start.dateは null になります。

よって、
format(yesterday) === event0.start.date はtrueです。
format(today) !== event1.start.date も trueです。

よって、if文が trueになり、配信再開になります。

昨日が祝日で、当日が祝日でないなら、配信再開したいからOKですよね。

③昨日が祝日ではないが当日が祝日

この場合もeventのitemsには1つ分のイベントしか入らないため、
event0 の方にだけ値が入ります(当日の祝日イベント)。
event1は{start: {date: null}}がセットされますね。

設定としては

昨日:2023年5月2日(祝日でない)
当日:2023年5月3日(憲法記念日)

とします。

format(yesterday)は "2023-05-02"になり、
format(today)は "2023-05-03"になります。

calendarEvents.itemsの中には
『当日の祝日
』である1つしか入ってません。

そのため、
event0.start.date は "2023-05-03" が入ります。
(当日が祝日のため、当日の日付が入ります。)

calendarEvents.items[1]ははundefinedのため、
event1は{start: {date: null}} になり、
event1.start.date は null になります。

よって、
format(yesterday) === event0.start.date は false です。
format(today) !== event1.start.date は trueです。

event0.start.dateの方には『当日日付』が入っているため、
format(yesterday) (昨日) とは異なります。

後者は、"2023-05-03" と nullの比較で
等しくないので true になります。

if文全体で false になるので、配信再開はしません。

④昨日も当日も祝日ではない
itemsは空なのでevent0もevent1も{start: {date: null}}になります。

この場合は、event0.start.dateもeven1.start.dateともに 「null」になります。

よって、format(yesterday) === event0.start.dateの比較が false にり、配信再開は行われません。

というわけで、②昨日が祝日で当日は祝日ではない
の場合だけ配信再開するわけです。

コードは以上になります。

頻度は「毎日 0:00~1:00」とかで設定すればOKかと思います。

以上です。
次回は、Yahoo!広告における祝日の自動オンオフについて書きたいと思います。

おまけ

ここまで読んでいて、「再開の場合」に疑問を持った人もいるかもしれません。

「昨日が祝日 かつ 当日は祝日ではない」場合に再開
じゃなくて、
「当日が祝日ではない」だけの条件で良くない?と。

最初から「オン」の場合に、再開のスクリプトが走っても問題ないわけですから、「当日が祝日ではない」条件だけでいいような気もします。

というか正直OKです(笑)

ただ、祝日ではない = 平日 ってつまり「ほとんど」になりますので
言ってしまえば余計なスクリプトがガンガン動くことになります。

それがなんとなく嫌だったので、if文で制限した形になります。
皆さんはやりやすいように作っていただいて大丈夫かと思います。

逆に、厳密にやろうとするなら「停止」の場合も
「翌日が祝日」だけでなく、「当日が祝日ではない かつ 翌日が祝日」という条件にしようよ!って意見もあるかと思います。

つまり連休の場合は起動させないってことですね。
当日が祝日の場合は既に停止しているわけですから
スクリプトを動かすのは「当日が祝日ではない かつ 翌日が祝日」と条件を狭くできるわけです。

ただ、再開の例は、「祝日ではない = 平日」にガンガンスクリプト動くのが個人的に微妙でしたが、「祝日」に余計に動く分には許容範囲かな、、という考えですw

このあたりはお好きに改編してくださいませ!

本当に以上です。

Bye, bye.

上記のようなお悩みを持つ企業様にとってピッタリなサービスとなっておりますので、少しでも興味を持たれた方はこちらからお問い合わせをお願い致します。

もし、私たちの会社で働く事に少しでも興味を持っていただけたら、ぜひ応募フォームよりご連絡ください。

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