Googleカレンダーの繰り返しイベントの仕様を確認する

## はじめに
表題通り、Googleカレンダーの繰り返しイベントの仕様を確認していきます。
実際にカレンダーにデータを導入して、動かしながら確認します。

## 事前準備
今回は、Google CalendarのEventListを動かします。その際に`Google Calendar API Explorer`を利用すると面倒な処理なしでレスポンスの結果が確認できます。

https://developers.google.com/workspace/calendar/api/v3/reference/events/list?hl=ja&apix_params=%7B%22calendarId%22%3A%22primary%22%2C%22singleEvents%22%3Atrue%2C%22timeMax%22%3A%222025-06-30T00%3A00%3A00Z%22%2C%22timeMin%22%3A%222025-06-01T00%3A00%3A00Z%22%7D


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/0b6911ef-bede-4eb7-98b8-899839935a24.png)
上の図の赤枠のAPIのボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/25ae1c6e-64e2-44ae-b958-cc165e7e451c.png)
Executeボタンを押すと、結果が返却されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/7cddfa6b-5a3f-49e8-8c05-3a1f60f35b29.png)
Googleの認証が走ります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/25c5cc8e-e10c-4691-8245-c15f885ce7db.png)
→実際にResponseのデータが返却されます

## Hands-on
毎週月曜日のイベントを設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/350662bb-9720-42de-be68-c1fc64a1d663.png)

→関係のある箇所だけ抜粋します。繰り返し予約に関係あるのは、itemsのみです。

```json:
{
  ....// 省略。
  "items": [
    {
      "id": "4h4t3culk1t1dk3q7mmaoa8gm8_20250609T010000Z",
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-09T10:00:00+09:00"
      },
      "end": {
        "dateTime": "2025-06-09T10:30:00+09:00"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-09T10:00:00+09:00"
      }
    },
    {
      "id": "4h4t3culk1t1dk3q7mmaoa8gm8_20250616T010000Z",
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-16T10:00:00+09:00"
      },
      "end": {
        "dateTime": "2025-06-16T10:30:00+09:00"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-16T10:00:00+09:00"
      }
    },
    {
      "id": "4h4t3culk1t1dk3q7mmaoa8gm8_20250623T010000Z",
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-23T10:00:00+09:00"
      },
      "end": {
        "dateTime": "2025-06-23T10:30:00+09:00"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-23T10:00:00+09:00"
      }
    }
  ]
}
```

## 単発のイベントを追加。
繰り返しイベントと単発イベントの比較を実施する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/9ee6d994-67d2-4f5c-a3d7-78f08876578c.png)


```json:
{
	"items": [
		{
			"summary": "繰り返しのイベント",
			"start": {
				"dateTime": "2025-06-09T10:00:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"end": {
				"dateTime": "2025-06-09T10:30:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
			"originalStartTime": {
				"dateTime": "2025-06-09T10:00:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"eventType": "default"
		},
		{
			"summary": "繰り返しのイベント",
			"start": {
				"dateTime": "2025-06-16T10:00:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"end": {
				"dateTime": "2025-06-16T10:30:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
			"originalStartTime": {
				"dateTime": "2025-06-16T10:00:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"eventType": "default"
		},
		{
			"summary": "繰り返しのイベント",
			"start": {
				"dateTime": "2025-06-23T10:00:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"end": {
				"dateTime": "2025-06-23T10:30:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
			"originalStartTime": {
				"dateTime": "2025-06-23T10:00:00+09:00",
				"timeZone": "Asia/Tokyo"
			},
			"eventType": "default"
		},
		{
			"summary": "単発のイベント",
			"start": {
				"date": "2025-06-10"
			},
			"end": {
				"date": "2025-06-11"
			},
			"eventType": "default"
		}
	]
}
```

:::note info
単発のイベントの場合は、`recurringEventId`がないんですね。
:::


## 繰り返し予約のイベントの変更(単発の変更)
毎週月曜日の繰り返し予約のイベントの中で、6月16日を6月17日に変更。わかりやすいように名称も変更している

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/7f93e4eb-51a3-4294-9688-032e2e15b660.png)


```json:
{
  "items": [
    {
      "summary": "単発のイベント",
      "start": {
        "date": "2025-06-10"
      },
      "end": {
        "date": "2025-06-11"
      },
      "eventType": "default"
    },
    {
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-09T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-09T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-09T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "eventType": "default"
    },
    {
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-23T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-23T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-23T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "eventType": "default"
    },
    {
      "summary": "予定変更：繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-17T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-17T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-16T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "eventType": "default"
    }
  ]
}
```
→`recurringEventId`は元々のやつと一致しています。

## 繰り返し予約のイベントの変更(これ以降のすべての予定)
繰り返しイベントの中で、いついつ以降の繰り返しイベントのルールを変更する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/cf285caf-481b-406f-a4ae-6e618cd384d3.png)

```json:

{
  "items": [
    {
      "summary": "単発のイベント",
      "start": {
        "date": "2025-06-10"
      },
      "end": {
        "date": "2025-06-11"
      },
      "eventType": "default"
    },
    {
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-09T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-09T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-09T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "eventType": "default"
    },
    {
      "summary": "予定変更：繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-17T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-17T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-16T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "eventType": "default"
    },
    {
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-25T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-25T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurringEventId": "9b15hqr7q9ofe8l5emucimhu6l",
      "originalStartTime": {
        "dateTime": "2025-06-25T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "eventType": "default"
    }
  ]
}
```
→ここで注目なのが、recurringEventIdが元のイベントから切り替わっていますね。繰り返しイベントの中で繰り返しイベントを作成すると、それは元のイベントからは切り離されるのですかね？？🤔

→singleEventsをfalseにしてみてみます。

```json:
{
  "items": [
    {
      "summary": "単発のイベント",
      "start": {
        "date": "2025-06-10"
      },
      "end": {
        "date": "2025-06-11"
      },
      "eventType": "default"
    },
    {
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-09T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-09T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurrence": [
        "RRULE:FREQ=WEEKLY;UNTIL=20250622T145959Z;BYDAY=MO"
      ],
      "eventType": "default"
    },
    {
      "summary": "予定変更：繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-17T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-17T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurringEventId": "4h4t3culk1t1dk3q7mmaoa8gm8",
      "originalStartTime": {
        "dateTime": "2025-06-16T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "eventType": "default"
    },
    {
      "summary": "繰り返しのイベント",
      "start": {
        "dateTime": "2025-06-25T10:00:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "end": {
        "dateTime": "2025-06-25T10:30:00+09:00",
        "timeZone": "Asia/Tokyo"
      },
      "recurrence": [
        "RRULE:FREQ=WEEKLY;BYDAY=WE"
      ],
      "eventType": "default"
    }
  ]
}
```
→確かに、マスターのイベントが切り替わっていますね。
これはすごく面白いですね。
`"RRULE:FREQ=WEEKLY;UNTIL=20250622T145959Z;BYDAY=MO"`になっています。
これ以降の予定は変更するって事を入れた事で、UNTILを含めて制御しているんですね。納得。
ちなみに、最初の繰り返しを消すとどうなるのかみてみましょう。

![google_calendar.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/ea61c2c7-e499-43f4-8893-26920ed5b444.gif)
→元の繰り返し予約のイベントを削除した場合でも、派生先の繰り返しイベントは削除されません。
RRULEが切り替わったことで、元のイベントと派生元のイベントは切り離して考えているようですね。