【GAS】Google calendarの空き時間をテキストで出力
はじめに
こんにちは!
情報系の学部に所属する就活生です!
GASから簡単に自分のカレンダーにアクセスして、空き時間を出力するものを作りました!
以下のようにコンソールに出力されます!
10月26日(土)
終日可
10月28日(月)
08:00~10:00
10月29日(火)
08:00~11:00
12:30~14:30
15:30~19:00
10月30日(水)
08:00~10:00
18:00~19:00
序盤はこれを作成した経緯
後半にコードを載せています!
日程調節が時間かかる。。
就職活動が本格化し、(OBOG・就活エージェント・企業)様と連絡を取らせてもらう機会が増えた。それに伴って増えるのが、日程調節である。
「空いている日程をできるかぎり教えてください」というエージェントさんからのメッセージがトラウマになるほど自分のカレンダーと向き合った。
私はYahooカレンダーを使っていたので、スマホのカレンダーを見ながらパソコンに入力ということをしていた。
トラウマではないが、この時間少しもったいないな。もっと効率化できそうだな。と思った。
そこで私は、数々の便利なAPIを公開してくださっているGoogle様のカレンダーならAPIを使って、空いている時間を自動で抽出できそうだなと思い、Google calendarについて調べてみた。
Google calendar「予約スケジュール機能」
すると、Google calendarの標準機能で、「予約スケジュール機能」という神のような機能がついていた。
消費者の不便を解決する機能のさらに上をいくGoogle様のこの機能に驚いた。
簡単に「予約スケジュール機能」を説明すると、自分の空いている時間だけを表示するサイトを作成でき、そのURL共有することで、他の人に空いている時間を共有できる。加えて、そこからカレンダーに予定を作成することもできる。
↓こんな感じ
※詳しい使い方などは他の方がとてもわかりやすく解説してくださっているため、ここには書かない!
これでいいのか
その神機能を使って日程調節しようと、作成したURLを就活エージェントさんに送って、ふと思った。
これは、失礼なのか、?
というのも、エージェントさんから「空いている時間を複数送ってください」とお願いされて、「このURLから私の空き時間を確認できます!お願いします!」と返信する。
私からすると、簡単に空いている時間を見ることができるので、相手のことを思った施策だと思っていた。
私としては、この機能を使った方が、お互いの負担を減らせると思い、この方法を利用している。
私側は空いている日程をカレンダーから抽出する手間がなくなり、相手方も私の空いている日程をいつでも簡単に把握でき、予定の作成もできる。
実際に、何名かの就活エージェントさんから、「すごい!めちゃくちゃ便利です!」というお言葉をいただいた。
しかし、この方法だと、相手方に冷たい印象を与えてしまうと思った。また相手方は以下のようなフォーマットで送ってほしいと思っているのかもしれない。とも思い、今回のプログラムを作成した。
〇月〇日 12:00~18:00
〇月〇日 終日
〇月〇日 9:00~16:00
実際のプログラム
function main() {
// ※※※※※※※※ここ変更する※※※※※※※※
// 対象期間と時間帯を指定
const startDate = new Date('2024-10-19T08:00:00'); // 開始日
const endDate = new Date('2024-10-30T23:00:00'); // 終了日
const dayStartHour = 8; // 1日の開始時間
const dayEndHour = 19; // 1日の終了時間
const excludeHolidays = false; // 祝日や休日を除外するかどうかのフラグ
const ignoreSchoolEvents = false; // 学校という名前の予定を無視するかどうかのフラグ
// ※※※※※※※※ここ変更する※※※※※※※※
// カレンダーから空いている時間を抽出
const freeTimes = getFreeTimes(startDate, endDate, dayStartHour, dayEndHour, excludeHolidays, ignoreSchoolEvents);
// 空いている時間をフォーマットに従ってコンソールに出力
let output = "";
let lastDate = "";
freeTimes.forEach(time => {
const [date, period] = time.split(' ');
if (lastDate !== date) {
if (lastDate !== "") {
output += "";
}
output += `${date}\n`;
lastDate = date;
}
output += `${period}\n`;
});
console.log("\t実行開始");
console.log(output.trim());
console.log("\t実行完了");
}
function getFreeTimes(startDate, endDate, dayStartHour, dayEndHour, excludeHolidays, ignoreSchoolEvents) {
const calendar = CalendarApp.getDefaultCalendar();
const freeTimes = [];
let currentDate = new Date(startDate);
// 指定された日ごとにループ
while (currentDate <= endDate) {
if (excludeHolidays && isHoliday(currentDate)) {
currentDate = new Date(currentDate.setDate(currentDate.getDate() + 1));
continue;
}
const dayStart = new Date(currentDate.setHours(dayStartHour, 0, 0, 0));
const dayEnd = new Date(currentDate.setHours(dayEndHour, 0, 0, 0));
const events = calendar.getEvents(dayStart, dayEnd);
let availableStart = new Date(dayStart);
let allDayFree = events.length === 0; // イベントがない場合、終日空いていると仮定
// その日のイベントをソートし、空き時間を計算
events.sort((a, b) => a.getStartTime() - b.getStartTime());
events.forEach(event => {
if (ignoreSchoolEvents && event.getTitle().includes('学校')) {
return;
}
const eventStart = event.getStartTime();
const eventEnd = event.getEndTime();
if (availableStart < eventStart) {
freeTimes.push(formatFreeTime(currentDate, availableStart, eventStart));
}
if (availableStart < eventEnd) {
availableStart = new Date(eventEnd);
}
// イベントがあった場合は終日ではない
allDayFree = false;
});
if (allDayFree) {
// イベントが全くなかった場合は「終日可」を記録し、時間範囲は追加しない
freeTimes.push(`${formatDate(currentDate)} 終日可`);
} else if (availableStart < dayEnd) {
freeTimes.push(formatFreeTime(currentDate, availableStart, dayEnd));
}
// 翌日に移動
currentDate = new Date(currentDate.setDate(currentDate.getDate() + 1));
}
return freeTimes;
}
function isHoliday(date) {
const day = date.getDay();
if (day === 0 || day === 6) {
// 土曜日・日曜日は休日とみなす
return true;
}
const holidays = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com').getEventsForDay(date);
return holidays.length > 0;
}
function formatFreeTime(currentDate, startTime, endTime) {
const dayNames = ["日", "月", "火", "水", "木", "金", "土"];
const formattedDate = Utilities.formatDate(currentDate, Session.getScriptTimeZone(), "MM月dd日(" + dayNames[currentDate.getDay()] + ")");
const formattedStart = Utilities.formatDate(startTime, Session.getScriptTimeZone(), "HH:mm");
const formattedEnd = Utilities.formatDate(endTime, Session.getScriptTimeZone(), "HH:mm");
return `${formattedDate} ${formattedStart}~${formattedEnd}`;
}
// 日付のフォーマットを追加
function formatDate(currentDate) {
const dayNames = ["日", "月", "火", "水", "木", "金", "土"];
return Utilities.formatDate(currentDate, Session.getScriptTimeZone(), "MM月dd日(" + dayNames[currentDate.getDay()] + ")");
}
各種設定
- 開始日 (startDate):2024-10-19T08:00:00
これは空き時間の検索を開始する日時。この場合、2024年10月19日午前8時。 - 終了日 (endDate):2024-10-30T23:00:00
これは空き時間の検索を終了する日時。この場合、2024年10月30日午後11時。 - 1日の開始時間 (dayStartHour):8
この場合、毎日の空き時間のチェックは午前8時から開始される。 - 1日の終了時間 (dayEndHour):19
この場合、毎日の空き時間のチェックは午後7時で終了する。 - 祝日を除外するか (excludeHolidays):false
祝日や休日を空き時間のチェックから除外するかどうかを指定する。この場合、除外しない設定。 - 学校のイベントを無視するか (ignoreSchoolEvents):false
「学校」という名前のついたイベントを無視するかどうかを指定する。この場合、無視しない設定。
※学校という予定よりも大切な予定を立てる際に使用した。以下の部分を変更することで、ある名前の予定を無視する。というフラグにできる。
if (ignoreSchoolEvents && event.getTitle().includes('学校')) {
return;
}
出力
10月26日(土)
終日可
10月28日(月)
08:00~10:00
10月29日(火)
08:00~11:00
12:30~14:30
15:30~19:00
10月30日(水)
08:00~10:00
18:00~19:00
このように、指定した時間(この場合、8時から19時)全てが空いていた場合は、「終日可」となる。
最後に
私は、相手方に対して失礼になってしまうかもしれないと考え、Googleの「予約スケジュール機能」は使わず、このプログラムで空き時間を抽出して相手方に送る方法を選んでいます。
まだ社会も経験していない未熟者ではありますが、こうした細かな配慮や慎重な姿勢が、結果として日本のデジタル化を遅らせている一因かもしれない、と感じることがあります。
デジタル技術を活用しながらも相手への配慮を大切にし、より効率的な手法を見つけることが今後の課題だと感じています。
このプログラムももっと良いものにできると思うので、今後さらに改良していきたいと思います!
最後までご覧いただきありがとうございました!
参考元
- Googleカレンダーに新たな進化!「予約スケジュール機能」で日程調整がもっと便利に!
https://scheduling.receptionist.jp/blog/scheduling-tool-google/ - 予約スケジュールを作成する
https://support.google.com/google-workspace-individual/answer/10729749?hl=ja - 公式ドキュメント
https://developers.google.com/apps-script/reference/calendar?hl=ja - GASによるGoogleカレンダー操作方法まとめ(予定取得/作成/削除/追加/色など)
https://auto-worker.com/blog/?p=4430#
Discussion