l

o

a

d

i

n

g

.

.

.

日程調整の最適解

2026/07/17

AI アイデア コミュニケーション

t f B! P L
eyecatch 誰かとお打ち合わせの約束をするときに、自分の空き時間を探して、 2,3パターンの日時を相手に提示して、 相手がどれも都合が悪ければ、相手からまた、2,3パターン定時してもらって・・・ このやりとり、なんだか個人的にもどかしい、めんどくさい、もっと便利にならないものか? 中には、自分のカレンダーの空き時間を公開して、相手にその中から選んでもらうという、Webサービスもある。 ・・・が、これはこれで、おもてなし精神に欠ける気がするので、個人的には好きではないし、 同じくこれを嫌っている人も数多くいるという話を、よく聞く。 いったい、どうすりゃいいのか???という思考をまとめるために、ブログで整理しながら、自分なりの最適解を探してみたいと思います。

日程調整方法アラカルト

とりあえず、どんな方法があるのかを、リストアップしてみました。

昔ながらの日程提示

7/25 10:00~ 7/26 14:00~ 7/27 16:00~
かつてから一番やっている人が多い、この方法は、まず自分の空き日程を調べてから、 このフォーマットで記述して、相手に提示する。 できれば曜日も書くと間違いがなくなるハズですね。 メリット ・多くの人がやっている ・相手が選ぶだけなのでおもてなしになる デメリット ・メールやチャットの往復が何度か発生する ・候補が埋まることがある

カレンダー予約システム

カレンダー連携して、空き時間を相手に定時できるサービスは、以下のようなものがあります。 ・CalendlyTimeRexBookrun メリット ・ほぼ自動で自分は何もしなくてもいい ・ダブルブッキング防止ができる デメリット ・相手がURLを開く手間 ・相手が自分のカレンダーと比較する手間 ・少し機械的なフォーム入力 やはりこの方法は、「調整コストを相手に押し付けている」にも思えるから、営業色が強い人ほど嫌う人もいるんでしょうね。

候補日生成AI

最近増えてきているのは、今時のAIに全てを任せる方法。 AIが、 ・自分の予定を見る ・相手の予定を見る ・AIが候補を出す というやり方。 ただし、 ・Google Calendar ・Outlook ・Teams などの組織連携が前提になるので、同じ組織であれば比較的問題ないが、 ビジネスマッチングサービスなどの場合、相手のカレンダー情報を取り込む術が今の時点でない・・・。 なので、個人間ではまだ微妙・・・。

実は最も効率がいい方法

「いつ空いてます?」と聞くことですね。 相手にスケジュールを定時させるように促すから、一見非効率に見えるんですが、 ポイントは、 「今週・・・」とか「来週・・・」と、つけることで、1ヶ月以上先のスケジュールセットをしっかりとする印象から、 友達感覚に近い、「今週、金曜日の15時」ぐらいの、ピンポイントでのやり取りが可能になります。 もちろん、初対面の人だと、難しいかもしれませんが、知人であればこれの方が確実ですよね。 つまり、日程調整コストよりも、関係性コストの方が大きいとも考えられます。 仲良い人同士ほど雑になるということはわかるんですが、相手を選ぶこともあるので、最適解とは言えないかも・・・

AIに質問

この内容をそのままAIに質問して、返ってきた答えが・・・
ビジネスマッチングにおける日程の合わせ方の最適解は存在しない
でした・・・OMG・・・ ということで、自分として見出した最適解は、 「自分のカレンダーの空き時間をGASでリストアップする」 これです。 相手の日程と比べるのはそもそも万人向きでもないし、自分の全ての予定をカレンダー登録しているという人が多いわけではないという事実もある。 ビジネスマッチングでのお打ち合わせは、プライベートで行う場合が多いので、プライベートまでカレンダー登録している人もおそらくは非常に少ないとも思われますからね。 なので、自分の予定を3案提出するのを、AIにやってもらって、自分の工数も削減できるというのがいいかな・・・と思います。 最初はAIでいい感じの3日をピックアップしてもらおうとしたんだけど、カレンダーとAIを繋ぎ込むのがうまくできなさそうだったので、 いっそのこと、GASで直接自分の空き時間を表示するだけでいいのでは?と思って、次の方法に行きつきました。

GASコード

function doGet() { const calendar = CalendarApp.getDefaultCalendar(); const now = new Date(); const week = ["日","月","火","水","木","金","土"]; const lines = []; for (let day = 0; day < 31; day++) { const target = new Date(now); target.setDate(target.getDate() + day); const dow = target.getDay(); // 土日除外 if (dow === 0 || dow === 6) { continue; } const dayStart = new Date(target); dayStart.setHours(0, 0, 0, 0); const dayEnd = new Date(target); dayEnd.setHours(23, 59, 59, 999); const events = calendar.getEvents(dayStart, dayEnd); const busy = events.map(event => ({ start: event.getStartTime(), end: event.getEndTime() })); const freeRanges = []; // 午前 freeRanges.push( ...findFreeRanges( busy, adjustPastTime( new Date(target.getFullYear(), target.getMonth(), target.getDate(), 10, 0), now ), new Date(target.getFullYear(), target.getMonth(), target.getDate(), 12, 0) ) ); // 午後 freeRanges.push( ...findFreeRanges( busy, adjustPastTime( new Date(target.getFullYear(), target.getMonth(), target.getDate(), 13, 0), now ), new Date(target.getFullYear(), target.getMonth(), target.getDate(), 17, 0) ) ); if (freeRanges.length === 0) { continue; } lines.push( Utilities.formatDate( target, "Asia/Tokyo", "yyyy-MM-dd" ) + "(" + week[dow] + ")" + freeRanges.join(", ") ); } return ContentService .createTextOutput(lines.join("\n")) .setMimeType(ContentService.MimeType.TEXT); } function findFreeRanges(busy, rangeStart, rangeEnd) { if (rangeStart >= rangeEnd) { return []; } const overlaps = busy .filter(b => b.end > rangeStart && b.start < rangeEnd ) .sort((a, b) => a.start - b.start); const result = []; let cursor = new Date(rangeStart); overlaps.forEach(b => { if (b.start > cursor) { const minutes = (b.start - cursor) / 1000 / 60; if (minutes >= 60) { result.push( formatTime(cursor) + "〜" + formatTime(b.start) ); } } if (b.end > cursor) { cursor = new Date(b.end); } }); if (cursor < rangeEnd) { const minutes = (rangeEnd - cursor) / 1000 / 60; if (minutes >= 60) { result.push( formatTime(cursor) + "〜" + formatTime(rangeEnd) ); } } return result; } function adjustPastTime(baseTime, now) { if ( baseTime.getFullYear() !== now.getFullYear() || baseTime.getMonth() !== now.getMonth() || baseTime.getDate() !== now.getDate() ) { return baseTime; } if (baseTime > now) { return baseTime; } const adjusted = new Date(now); // 30分単位に切り上げ const minutes = adjusted.getMinutes(); if (minutes === 0) { return adjusted; } adjusted.setMinutes( Math.ceil(minutes / 30) * 30, 0, 0 ); return adjusted; } function formatTime(date) { return Utilities.formatDate( date, "Asia/Tokyo", "HH:mm" ); } このコードをGASに貼り付けて、デプロイ登録をして、出来上がったURLアプリにアクセスすると、以下のような結果がブラウザに表示されるようになります。 2026-06-23(火)10:00〜12:00, 13:00〜17:00 2026-06-24(水)10:00〜12:00, 13:00〜14:00, 15:00〜17:00 2026-06-26(金)10:00〜12:00, 13:00〜17:00 2026-06-29(月)13:00〜17:00 2026-07-02(木)10:00〜12:00, 13:00〜17:00 2026-07-03(金)10:00〜11:00, 15:00〜17:00 2026-07-06(月)13:00〜17:00 2026-07-07(火)10:00〜12:00, 13:00〜17:00 2026-07-08(水)10:00〜12:00, 13:00〜17:00 2026-07-09(木)10:00〜12:00, 13:00〜17:00 2026-07-10(金)10:00〜12:00, 13:00〜17:00 2026-07-13(月)13:00〜17:00 2026-07-14(火)10:00〜12:00, 13:00〜17:00 2026-07-16(木)10:00〜12:00, 13:00〜17:00 2026-07-17(金)10:00〜12:00, 13:00〜17:00 2026-07-20(月)13:00〜17:00 2026-07-21(火)10:00〜12:00, 13:00〜17:00 2026-07-22(水)10:00〜12:00, 13:00〜17:00 2026-07-23(木)10:00〜12:00, 13:00〜17:00 1ヶ月分の平日の空き時間一覧です。 この中から3つをピックアップ(コピペ)して相手に伝えるだけ。

あとがき

これまで、日程調整でめんどくさいやり取りをしていましたが、 この方法が一番相手にも負担をかけずに、嫌な思いをさせない、されない方法であると確信できました。 ザッツ・効率!

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ