はじめに
皆さんはSystems ManagerのChange Calendarという機能をご存知でしょうか?
名前の通り、SSMで使用するカレンダーを管理することができる機能です
このChange Calendarには日本の祝日データがないため、サードパーティからエクスポートしてきたものを使用する必要があります
その際に、Change Calendarにインポートすると時間設定がずれてしまう事象が発生したので、解決方法を含めて記載しようと思います
Change Calendarについての概要
Change Calendarは上記の通り、SSMで使用するカレンダーをユーザーが作成・管理できる機能です
AutomationやRun Commandと組み合わせると、カレンダーの状態に応じて異なるアクションを実行させることができます
例えば、Change Calendarに日本の祝日データを登録しておいて、祝日の場合はインスタンスを起動させないタスクを定義することなどができます
非常に便利な機能なのですが、各国の祝日データはSSMから提供されてないためサードパーティから入手する必要があります
公式ドキュメントでは、以下の3つをサポートしているとの記載があります
- Google カレンダー
- Microsoft Outlook
- iCloud カレンダー
実施した作業
今回はGoogle カレンダーからの祝日データエクスポートと、Change Calendarへのデータインポートを実施しました
その手順を、簡単に紹介します
Google カレンダーからの祝日データエクスポート
まずはGoogleカレンダーの画面を開き、「関心のあるカレンダーを探す」をクリックします
「日本の祝日」にチェックを入れます
再度Googleカレンダーのトップ画面に戻り、追加した「日本の祝日」データの設定を開きます
カレンダーの統合の欄にある「iCal形式の公開URL」にアクセスすると、カレンダーのデータがダウンロードされます
Change Calendarへのデータインポート
AWSマネジメントコンソールでSystems Managerの画面を開き、左のメニューから「Change Calendar」をクリックします
新規にカレンダーを作成する場合は「カレンダーを作成」をクリックします
すでに存在するカレンダーを使う場合は、対象のカレンダーをクリックした後に「Action」から「編集」をクリックします
「Import calendar」という箇所でインポートさせたいカレンダーデータを選択したら「保存」をクリックします
発生した事象について
今回、Googleカレンダーからエクスポートした祝日データをChange Calendarにインポートしましたが、2つの問題がありました
- タイムゾーンがUTCになってしまう
- タイムゾーンを変更しても、各祝日の時間幅がズレてしまう
タイムゾーンがUTCになってしまう
タイムゾーンがUTCとしてインポートされてしまうので、時間が9時間ずれてしまいます
例えば、11/23(木)の勤労感謝の日が「11/23 AM9:00(JST)~11/24 AM9:00(JST)」と認識されてしまっていました
これの何が困るかというと、例えば時間を指定してタスクを実行する時に問題が発生します
例えば、祝日以外の朝8時にインスタンスを自動起動させたいとします
ただし、カレンダーデータのタイムゾーンがUTCだと、11/24(金)の朝9時までは祝日だと判定されてしまいます
となると、「11/24(金)の8:00は祝日だからインスタンスを起動しない」と判定されてしまい、望んだ挙動をしてくれません
祝日の時間幅がズレてしまう
「タイムゾーンがUTCになってしまう」の解決方法は下記に示しますが、これを解決しても「祝日の時間幅がズレてしまう」という問題が発生しました
タイムゾーンをAsia/Tokyoに変更しても、各祝日の詳細を見てみると日付を跨いでしまっていることがわかります
解決方法
AWSサポートへの問い合わせを元に調査を行った結果、Google カレンダーのデータそのものを編集する必要があると判明しました
具体的には以下の修正が必要とわかりました
- X-WR-TIMEZONEをAsia/Tokyoに変更する
- DTSTARTとDTENDに時刻を入力する
X-WR-TIMEZONEをAsia/Tokyoに変更する
Google カレンダーからそのままエクスポートすると.ics
という拡張子のデータが手に入ります
これをテキストエディタで開くと、以下のようになっています(長いので上10行だけ抜粋します)
BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:日本の祝日 X-WR-TIMEZONE:UTC X-WR-CALDESC:日本の祝日と行事 BEGIN:VEVENT DTSTART;VALUE=DATE:20220503
タイムゾーンを示すX-WR-TIMEZONE
がUTC
になっているので、Asia/Tokyo
に変更しました
BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:日本の祝日 X-WR-TIMEZONE:Asia/Tokyo X-WR-CALDESC:日本の祝日と行事 BEGIN:VEVENT DTSTART;VALUE=DATE:20220503
すると、タイムゾーンが東京に変わりました!
DTSTARTとDTENDに時刻を入力する
タイムゾーンを変更しても、各日にちの時間幅にはズレが発生しています
ここで再度ICSファイルを深掘りしてみると、DTSTART
およびDTEND
で始まる行の記載を編集する必要があるとわかりました
そもそも、ICSファイルはBEGIN:VEVENT
とEND:VEVENT
で挟まれた箇所が、各祝日のデータになっています
このBEGIN:VEVENT
とEND:VEVENT
で挟まれた箇所の塊が複数列挙されているのがICSファイルなのです
さらに、DTSTART
およびDTEND
は各祝日の開始日と終了日を示しています
Googleカレンダーからダウンロードしてきたデータを確認すると、以下のように日付を跨ぐような記載になっていました
BEGIN:VEVENT DTSTART;VALUE=DATE:20230101 DTEND;VALUE=DATE:20230102 DTSTAMP:20240119T004943Z UID:20230101_bi9rgohetsotqlilg2ou523bh8@google.com CLASS:PUBLIC CREATED:20220927T105018Z DESCRIPTION:祝日 LAST-MODIFIED:20220927T105018Z SEQUENCE:0 STATUS:CONFIRMED SUMMARY:元日 TRANSP:TRANSPARENT END:VEVENT
ということで、ICSファイルに以下のような処理を行えれば正確に時間幅を指定できると考えました
- BEGIN:VEVENTとEND:VEVENTで囲まれた範囲それぞれで以下を実施
- 現状のDTSTARTの末尾8文字を変数として定義
- DTSTARTとDTENDを以下に置き換える
- DTSTART;TZID=Asia/Tokyo:T000000
- DTEND;TZID=Asia/Tokyo:T235959
- ただし、最初の7行は処理の対象外にする
このような処理を行なってくれるPythonスクリプトを、ChatGPTにも手伝ってもらいながら作成しました
import re def process_vevent_block(block): date_match = re.search(r"DTSTART;VALUE=DATE:(\d{8})", block) if date_match: date = date_match.group(1) block = re.sub(r"DTSTART;VALUE=DATE:\d{8}", f"DTSTART;TZID=Asia/Tokyo:{date}T000000", block) block = re.sub(r"DTEND;VALUE=DATE:\d{8}", f"DTEND;TZID=Asia/Tokyo:{date}T235959", block) return block def process_calendar(input_text): lines = input_text.split('\n') output_lines = [] in_vevent_block = False current_block = "" for line in lines: if line.startswith("BEGIN:VEVENT"): in_vevent_block = True current_block = line + '\n' elif line.startswith("END:VEVENT"): in_vevent_block = False current_block += line processed_block = process_vevent_block(current_block) output_lines.append(processed_block) elif in_vevent_block: current_block += line + '\n' else: output_lines.append(line) result = '\n'.join(output_lines).rstrip('\n') # 末尾の改行を削除 result = re.sub(r"^\n", "-", result) # 先頭の改行を - に置換 return result file_path = "basic.ics" with open(file_path, "r", encoding="utf-8") as file: input_text = file.read() output_text = process_calendar(input_text) print(output_text)
これは作業ディレクトリにあるbasic.ics
という名前のファイルを読み込んで標準出力する形になっています
なので、以下のコマンドを実行することで別ファイルに書き込めます
$ python3 ics_time_change.py > output.ics
Change Calendarでインポートできるようにする
これで作成できたICSファイルをChange Calendarに読み込もうとすると、エラーになってしまいます
原因ははっきりとはわかりませんが、おそらく上記の処理でICSファイルが壊れてしまったと考えられます
なので、ICSファイルをGoogleカレンダーにインポートし、再度エクスポートするという手順を踏みました
具体的には以下の通りです
- Googleカレンダーで新しいカレンダーを作成する
- 新規作成したカレンダーに、編集したICSファイルをインポートする
- インポートが完了したら、新規作成したカレンダーをエクスポートする
- エクスポートしたICSファイルをChange Calenderでインポートする
この手順でうまくインポートすることができました!
時間幅のズレも解消されていることがわかります
注意点
Googleカレンダーの祝日データは、ダウンロードしてきたタイミングのデータしかないです
今後追加される祝日データについては反映されていないため、新しい祝日が追加されたら同じことをする必要があります
また、Googleの祝日データは約1年分しかないようです
実際、2023/12/15にダウンロードしてきたデータの中には2024/11までのデータしか存在しませんでした
なので、年に1度くらいのタイミングで祝日データを更新する必要がありそうです
まとめ
最初は、Change Calendar側で何か問題か設定漏れがあるのかと思いましたが、今回はICSファイルの中身を変更する必要がありました
ICSファイルの中身は長いですが、比較的簡単な構造をしているので使用する前に確認してみると良いと思います
なお、「もっといい方法あるよ!」など意見あれば是非教えて欲しいです!