はじめに
カレンダーの開発を実施する上で、インターネットの標準仕様に合わせて作るのが良いですね。
カレンダーの標準仕様はRFC5545で定義されています。開発する上で必ず目を通す必要はありますが、RFC5545は抽象的な部分があり、いまいちピンとこない部分があります。RFC5545→実装はなかなか難しいので、今回はRFC5545を実装に落とし込んだGoogleCalendarを元に学んでいきます!
Google Calendarシリーズ
Google CalendarAPIを学ぶ(Step1:APIの仕様、リソースを理解する)
GoogleCalendar APIを学ぶ(Step2:TBL作成、データ挿入)
GoogleCalendar APIを学ぶ(Step3:カレンダーの取得APIを実装する)
GoogleCalendar APIを学ぶ(Step4:カレンダーに紐づくイベントの一覧取得APIを実装する)
Google Calendar API
Google Calendar APIの概要をつかみましょう。
→こんだけ書かれていてもよくわからないですね😅 1つづつ理解していきましょう。
まずは用語の意味と関係性を示していきます
用語の理解
要素 | 説明 |
---|---|
ユーザー | Google カレンダーを利用する主体。1アカウントにつき1つ。 |
カレンダーリスト | ユーザーが左側ペインに登録しているカレンダーの一覧。表示・非表示や色・通知設定などユーザー固有のメタデータを管理。 |
カレンダー | イベント(予定)を格納するコンテナ。タイムゾーン、説明文、デフォルトの通知設定などを保持。 |
イベント | 単発予定または繰り返し予定の個別インスタンス。開始・終了時刻、参加者、リマインダーなどを含む。 |
設定 | ユーザー全体の環境設定(週の開始曜日、既定リマインダー方法など)。1ユーザーにつき1件。 |
ACL | カレンダーへのアクセス権を表すルール(owner/writer/reader)。ユーザーやグループ単位で付与。 |
関係性
関係 | 意味 |
---|---|
ユーザー → カレンダーリスト |
1 ユーザーが複数のカレンダーリストエントリを持つ |
カレンダーリスト → カレンダー |
1 エントリは 1 つのカレンダーを参照 |
カレンダー → イベント |
1 カレンダーに複数のイベントが属する |
ユーザー → 設定 |
ユーザーごとに 1 件の設定情報が紐づく(1:1) |
ユーザー -.-> ACL |
ユーザーやグループに ACL ルールが複数紐づく(点線) |
ACL -.-> カレンダー |
各 ACL ルールが対象カレンダーへのアクセス権を定義(点線) |
- 実線 (
-->
) は 所有 や 1:N の構造的な関係を示します。 - 点線 (
-.->
) は アクセス権付与 のような「強制ではないが関連する」関係を示します。
ふわっとした理解ができましたね。次はリソースごとに理解していこうと思います。
リソースの理解
カレンダーのリソース
{
"kind": "calendar#calendar", // リソース種別: カレンダーリソースを表す固定文字列
"etag": "\"XpAbC1234yz\"", // バージョン管理用タグ(例: 変更ごとに自動生成される ETag)
"id": "primary", // カレンダーID("primary" はメインカレンダー、セカンダリなら "xxx@group.calendar.google.com")
"summary": "Team Project Calendar", // カレンダー名(例: チーム用プロジェクトカレンダー)
"description": "カンファレンスや締め切りを共有するためのチーム用カレンダーです。",
// 説明文(例: 用途や対象を記載)
"location": "Tokyo Headquarters", // 所在地(フリーフォーム、例: 本社所在地)
"timeZone": "Asia/Tokyo", // IANA タイムゾーン名(例: Asia/Tokyo)
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet", // Google Meet を有効化
"eventHangout" // イベントハングアウト(旧 Hangouts)を有効化
]
}
}
プロパティ | 型/値例 | 説明 |
---|---|---|
kind |
string "calendar#calendar"
|
リソース種別を示す固定文字列 |
etag |
string "\"XpAbC1234yz\""
|
現バージョンを示すエンティティタグ(変更検出・楽観的排他制御用) |
id |
string "primary"
|
カレンダーの一意識別子(主に API 呼び出し時のキー) |
summary |
string "Team Project Calendar"
|
カレンダー名(タイトル) |
description |
string "カンファレンスや締め切りを共有するチーム用カレンダー"
|
カレンダーの詳細説明 |
location |
string "Tokyo Headquarters"
|
カレンダーの所在地情報(フリーフォーム) |
timeZone |
string "Asia/Tokyo"
|
カレンダーのデフォルトタイムゾーン(IANA 名) |
conferenceProperties | object |
会議リンク機能に関する設定オブジェクト |
└─ allowedConferenceSolutionTypes |
string[] ["hangoutsMeet","eventHangout"]
|
使用を許可する会議ソリューションの種類リスト |
カレンダーリストのリソース
{
"kind": "calendar#calendarListEntry", // リソース種別:カレンダーリストエントリ
"etag": "\"LmNoP5678qrs\"", // バージョン管理用タグ
"id": "team-shifts@example.com", // カレンダーID(グループカレンダーなど)
"summary": "Team Shifts", // カレンダーリスト上の表示名
"description": "部署の勤務シフトを管理するカレンダー", // カレンダーの詳細説明
"location": "Tokyo Office", // 場所
"timeZone": "Asia/Tokyo", // タイムゾーン
"summaryOverride": "My Shifts", // ユーザーが上書きしたカレンダー名
"colorId": "7", // 色ID(colorsリソース参照)
"backgroundColor": "#9ae2f1", // 背景色
"foregroundColor": "#000000", // 文字色
"hidden": false, // 非表示フラグ
"selected": true, // 左ペインで選択中かどうか
"accessRole": "writer", // 自分に付与された権限(owner/writer/reader/...)
"defaultReminders": [
{
"method": "popup", // リマインダー方法
"minutes": 30 // イベント開始30分前
}
],
"notificationSettings": {
"notifications": [
{
"type": "eventCreation", // イベント作成通知
"method": "email" // 通知方法
},
{
"type": "eventChange", // イベント変更通知
"method": "popup" // 通知方法
}
]
},
"primary": false, // プライマリカレンダーかどうか
"deleted": false, // リストエントリが削除済みかどうか
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet", // Google Meet
"eventHangout" // イベントハングアウト
]
}
}
設定
{
"kind": "calendar#setting", // リソース種別: 設定リソースを表す固定文字列 [oai_citation:0‡Google for Developers](https://developers.google.com/workspace/calendar/api/v3/reference/settings)
"etag": "\"ZmNoEnEtAg0123\"", // バージョン管理用タグ(例: 設定変更ごとに変わる ETag) [oai_citation:1‡Google for Developers](https://developers.google.com/workspace/calendar/api/v3/reference/settings)
"id": "timeZone", // 設定キー: ユーザーのタイムゾーンを示す設定 ID
"value": "Asia/Tokyo" // 設定値: IANA タイムゾーン名 [oai_citation:2‡Google for Developers](https://developers.google.com/workspace/calendar/api/v3/reference/settings?utm_source=chatgpt.com)
}
プロパティ | 型/値例 | 説明 |
---|---|---|
kind |
string "calendar#setting"
|
リソース種別を示す固定文字列 |
etag |
string "\"ZmNoEnEtAg0123\""
|
リソースのバージョンを表す ETag。変更検出や楽観的排他制御に利用 |
id |
string "timeZone"
|
設定項目を識別するキー(例:autoAddHangouts 、weekStart 、timeZone ) |
value |
string "Asia/Tokyo"
|
設定値。設定 ID によって文字列/数値/JSON 形式などが異なる |
ACL
{
"kind": "calendar#aclRule", // リソース種別: ACLルール
"etag": "\"AbCdEf123456\"", // バージョン管理用ETag(変更ごとに変わるタグ)
"id": "rule_123abc", // このACLエントリの一意ID
"scope": {
"type": "user", // 対象タイプ: user (特定ユーザー)
"value": "alice@example.com" // 対象ユーザーのメールアドレス
},
"role": "writer" // 付与する権限: writer (読み書き可)
}
プロパティ | 型/値例 | 説明 |
---|---|---|
kind |
string "calendar#aclRule"
|
リソース種別を示す固定文字列 |
etag |
string "\"AbCdEf123456\""
|
リソースのバージョンを示す ETag |
id |
string "rule_123abc"
|
この ACL ルールの一意識別子 |
scope.type |
string "user"
|
ルール適用対象のタイプ(user /group /domain /default ) |
scope.value |
string "alice@example.com"
|
user /group /domain の場合の識別子 |
role |
string "writer"
|
付与する権限レベル(owner /writer /reader /…) |
ACL Rule の scope.value
詳細
scope.value
は scope.type
に応じて以下のような形式・意味を持ちます。
scope.type | value の形式 | 意味 | 例 |
---|---|---|---|
user | メールアドレス文字列 | 特定ユーザーを対象 | alice@example.com |
group | Google グループのメール | 特定グループ(Google グループ)を対象 | dev-team@example.com |
domain | ドメイン名(ホスト部のみ) | ドメイン内のすべてのユーザーを対象 | example.com |
default | ―(省略または空文字) | 未認証ユーザーを含む全員に公開(公開権) | ※フィールド自体が返却されない |
-
user / group
- 値は実際に存在するユーザー/グループのメールアドレスである必要があります。
- グループは G Suite/Google Workspace のグループを指します。
-
domain
- 自社ドメイン全体にアクセスを許可したいときに使います。
- たとえば
example.com
とすると@example.com
ユーザー全員が対象です。
-
default
- 認証不要でカレンダーを公開したい(全世界に公開)場合に利用。
-
value
フィールドは返却されず、scope
オブジェクトは{ "type": "default" }
だけになります。
イベント
{
"kind": "calendar#event", // リソース種別
"etag": "\"abcd12345\"", // バージョン管理用 ETag
"id": "evt_12345", // イベント ID
"status": "confirmed", // 確定/仮予約/キャンセル
"htmlLink": "https://www.google.com/calendar/event?eid=evt_12345",
"created": "2025-05-17T10:00:00Z", // 作成日時
"updated": "2025-05-17T12:00:00Z", // 更新日時
"summary": "Project Kickoff", // タイトル
"description": "初回キックオフミーティング", // 詳細
"location": "オンライン", // 場所
"colorId": "6", // 色 ID
"creator": { // 作成者情報
"id": "user_123",
"email": "alice@example.com",
"displayName": "Alice",
"self": false
},
"organizer": { // 主催者情報
"email": "alice@example.com",
"displayName": "Alice",
"self": true
},
"start": { // 開始時刻
"dateTime": "2025-05-20T09:00:00Z",
"timeZone": "UTC"
},
"end": { // 終了時刻
"dateTime": "2025-05-20T10:00:00Z",
"timeZone": "UTC"
},
"recurrence": [ // 繰り返しルール
"RRULE:FREQ=WEEKLY;BYDAY=MO"
],
"recurringEventId": "evt_master_678", // シリーズイベントのマスター ID
"originalStartTime": { // 例外インスタンスの元開始時刻
"dateTime": "2025-05-20T09:00:00Z"
},
"transparency": "opaque", // ブロック状態
"visibility": "default", // 公開範囲
"iCalUID": "abcd12345@google.com", // iCal UID
"sequence": 2, // 更新シーケンス番号
"attendees": [ // 出席者一覧
{
"email": "bob@example.com",
"displayName": "Bob",
"responseStatus": "accepted",
"optional": false
}
],
"reminders": { // リマインダー設定
"useDefault": false,
"overrides": [
{
"method": "popup",
"minutes": 10
}
]
}
}
プロパティ | 型/値例 | 説明 |
---|---|---|
kind |
string "calendar#event"
|
リソース種別を示す固定文字列 |
etag |
string "\"abcd12345\""
|
リソースのバージョンを示す ETag(変更検出・楽観的排他制御用) |
id |
string "evt_12345"
|
イベントの一意識別子 |
status |
string "confirmed"
|
イベントの状態(confirmed /tentative /cancelled ) |
htmlLink |
string "https://www.google.com/calendar/event?eid=evt_12345"
|
ブラウザで開くイベント詳細ページの URL |
created |
string "2025-05-17T10:00:00Z"
|
イベント作成日時 |
updated |
string "2025-05-17T12:00:00Z"
|
最終更新日時 |
summary |
string "Project Kickoff"
|
イベントタイトル |
description |
string "初回キックオフミーティング"
|
イベント詳細 |
location |
string "オンライン"
|
イベント開催場所 |
colorId |
string "6"
|
イベント色の識別子(colors リソース参照) |
creator |
object {email, displayName, self}
|
作成者情報(メール・表示名・自己フラグなど) |
organizer |
object {email, displayName, self}
|
主催者情報(メール・表示名・自己フラグなど) |
start |
object {dateTime:"2025-05-20T09:00:00Z", timeZone:"UTC"}
|
開始時刻情報(日時+タイムゾーン) |
end |
object {dateTime:"2025-05-20T10:00:00Z", timeZone:"UTC"}
|
終了時刻情報(日時+タイムゾーン) |
recurrence |
string[] ["RRULE:FREQ=WEEKLY;BYDAY=MO"]
|
繰り返しルールの配列 |
recurringEventId |
string "evt_master_678"
|
繰り返しマスターイベントの ID |
originalStartTime |
object {dateTime:"2025-05-20T09:00:00Z"}
|
例外インスタンスの元の開始時刻 |
transparency |
string "opaque"
|
ブロック状態(opaque =予定ブロック、transparent =空き扱い) |
visibility |
string "default"
|
公開範囲(default /public /private /confidential ) |
iCalUID |
string "abcd12345@google.com"
|
iCalendar 標準の UID |
sequence |
integer 2
|
更新ごとのシーケンス番号 |
attendees |
object[] [{email,displayName,responseStatus,optional}]
|
出席者リスト(メール・表示名・応答状態・オプション参加など) |
reminders |
object {useDefault:false,overrides:[{method:"popup",minutes:10}]}
|
リマインダー設定(既定利用フラグ+個別オーバーライド設定) |
イベントリソースの拡張フィールド解説
以下では、recurrence
、recurringEventId
、originalStartTime
、iCalUID
、transparency
、visibility
の6つのフィールドについて詳しく説明します。
1. recurrence
-
型:
string[]
-
例:
["RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR"]
-
説明:
iCalendar 標準の繰り返しルールを表す文字列の配列。-
RRULE:
(必須)で繰り返しの頻度や曜日を指定 -
EXDATE:
で除外日、RDATE:
で追加日を指定可能
-
-
用途:
毎週○曜日や毎月○日などの繰り返し予定を定義し、events.instances
メソッドで個々のインスタンスを取得する
2. recurringEventId
-
型:
string
-
例:
"evt_master_678"
-
説明:
繰り返しシリーズのマスターイベント(元のイベント)のid
。
このフィールドが存在する場合、そのイベントは例外インスタンス(時間変更やキャンセルされた回)であることを示します。 -
用途:
例外インスタンスをマスターイベントと紐づけて管理するために使用します。
3. originalStartTime
-
型:
object
{
"dateTime": "2025-05-20T09:00:00Z",
"timeZone": "UTC"
}
-
説明:
例外インスタンスの「元々の開始時刻」を保持するオブジェクトです。 -
用途:
変更前の予定日時を把握したいときに使用されます。UI での表示や履歴の記録にも有用です。
4. iCalUID
-
型:
string
"abcd12345@google.com"
-
説明:
iCalendar 標準仕様に基づく UID。Google Calendar がイベントを外部カレンダーと同期する際に使用されます。 -
用途:
Outlook や Apple カレンダーなどとのイベント同期の際に一貫性を保つために活用されます。
5. transparency
-
型:
string
"opaque" // 予定あり(ブロック)
"transparent" // 空き時間扱い
-
説明:
Free/Busy 情報におけるブロック状態の制御設定です。 -
用途:
会議のスケジュール候補を表示する際に、他の予定と重ならない時間帯を探すロジックに使用されます。
6. visibility
-
型:
string
"default" // カレンダー既定に従う
"public" // 誰でも詳細を閲覧可
"private" // 詳細を隠して「予定あり」のみ表示
"confidential" // より制限された公開レベル
-
説明:
イベントの公開レベルを制御するための設定です。 -
用途:
他ユーザーにどこまで情報を見せるか(または見せないか)を制御し、機密性を保ちつつ共有できます。
最後に
ここまででGoogle Calendarの仕様がわかってきました。次のStepでは、実際にTBLの設計をしていこうと思います