子フロー：年間休日カレンダー取得

# きっかけ
PowerAutomateで休日（祝日）を取得する処理を検索したのですが、自社環境で使えないアクションが必要、データ更新の手間があるなど、思った通りのものがヒットしませんでした。
過去の祝日は求めない、皇室イベントなど不定イベントや法改正は考慮しないなど、妥協すれば計算式で算出できます。[PowerAutomate：子フローの作成](https://qiita.com/shutaro_y/items/d1f2e208d26584a08b1a)  をベースに祝日＋会社休日を取得するフローを作ったので内容を紹介します。

＜親フロー処理内容＞
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/4b2f7537-7f20-cb8f-7653-11d75417ff12.png">

＜実行結果＞
子フローの返り値として祝日配列を受け取れてます。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/9133e196-5216-b108-266c-56415c9d64bd.png">

# 祝日について
フローを作るためには祝日を理解しなければいけません。内閣府HPで公開されている祝日情報をフロー化するため種類別に分類します。

https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html

### 日付固定の祝日
| 日付 | 名称 | 備考 |
|:---:|:---:|:---|
|01/01|元日|1948年より適用|
|02/11|建国記念の日|1967年より適用|
|02/23|天皇誕生日|2020年より適用(令和天皇) <br>平成天皇は12/23、昭和天皇は4/29|
|04/29|昭和の日|1948年より適用 <br>1988年までは天皇誕生日、2006年まではみどりの日|
|05/03|憲法記念日|1948年より適用|
|05/04|みどりの日|1988-2006年までは国民の祝日(日曜除く) <br>1989-2006年は4/29がみどりの日|
|05/05|こどもの日|1948年より適用|
|08/11|山の日|2016年より適用 <br> 東京オリンピックの影響で2020年は8/10、2021年は8/8に変更|
|11/03|文化の日|1948年より適用|
|11/23|勤労感謝の日|1948年より適用|

### 日付可変の祝日（ハッピーマンデー・春分/秋分の日）
| 日付 | 名称 | 備考 |
|:---:|:---:|:---|
|01月第2月曜|成人の日|1948年制定時は固定日（1/15）<br> 2000年以降は1月第2月曜に変更|
|03月特定日|春分の日|1948年より適用<br>3/19-22のいずれかになり、国立天文台『暦象年表』に基づき前年2月頃に公示|
|07月第3月曜|海の日|1996年施行時は固定日（7/20）<br> 2003年以降は7月第3月曜に変更|
|09月第3月曜|敬老の日|1966年施行時は固定日（9/15）<br> 2003年以降は9月第3月曜に変更|
|09月特定日|秋分の日|1948年より適用<br>9/22-24のいずれかになり、国立天文台『暦象年表』に基づき前年2月頃に公示|
|10月第2月曜|スポーツの日|1966年施行時は固定日（10/10）<br> 2000年以降は10月第2月曜に変更 <br>東京オリンピックの影響で2020年は7/24、2021年は7/23に変更|

「春分・秋分の日」は地球の状況によって変わる可能性がありますが、計算ロジックによって算出できます。

https://eco.mtk.nao.ac.jp/koyomi/topics/html/topics1997.html

### その他の祝日
* 振替休日
1973年以降、祝日が日曜の場合はその直後の平日が該当。
通常は月曜になりますが、5/3-5は2007年以降、すべて祝日のためどれかが日曜となる場合は5/6が月曜以外でも祝日となる。（例：5/3(日)の場合、5/4-5も祝日のため5/6(水)が振替休日）
* 皇室慶弔行事に伴う休日
結婚、大葬、即位等の皇室行事が行われる日
* 国民の休日
1988年以降の2つの祝日に挟まれた平日。2006年までは5/4が該当したが、2007年に5/4は祝日となったため対象外に。2003年以降、敬老の日がハッピーマンデーになったことで、秋分の日が水曜になると適用される。


# 子フロー全体像
スコープ含めた各パートの詳細内容は後述します。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/c80b8ee8-e41f-ded0-c760-1f9b64d731db.png">

# ①トリガ設定
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/6605f0c1-f8a4-a55d-cef9-917ae83ae1ff.png">

親フローからは2つの引数を渡します。
* [任意：数値] 対象年
休日を取得する西暦年を指定します。指定がない場合は当年情報を取得できるようにします。
* [任意：はい/いいえ] 会社休暇
休日情報に会社休暇を含めるかを指定します。

# ②変数初期化
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/ae9eb8f9-55a0-ea42-6678-28653f36741a.png">

『変数を初期化する』で2つの変数を設定します。初期値設定するのは1つです。

* 対象年
本日日付の西暦年を初期値として設定します。
`@{int(convertTimeZone(utcNow(),'UTC','Tokyo Standard Time','yyyy'))}`

# ③入力対象年＝空値？
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/4bf36dda-95ba-ba5e-3bef-aded1d520f88.png">

『条件』で入力対象年の空値判定をします。
* 左辺： (式)`triggerBody()?['number']` 
* 条件：次の値に等しい
* 右辺： (式)`null`

条件分岐以下の構成は以下の通りです。
<img width="1200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/62cc9527-5471-2269-2f89-273f53e04bdd.png">

---
### ③-2) 入力値の範囲チェック
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/c416298d-cf1c-a287-c033-75a8a61b44d6.png">

『条件』で入力値の範囲を制限します。2021年以前は現在と異なる祝日ルールが含まれるため、2022年以降のみ指定できるようにします。
* And (条件1)
  * 左辺： (式)`triggerBody()?['number']` 
  * 条件：次の値以上
  * 右辺： 2022
* And (条件2)
  * 左辺： (式)`triggerBody()?['number']` 
  * 条件：次の値以下
  * 右辺： 2050
 
---
### ③-3) 対象年の設定
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/9c1fee92-69a5-3a90-e70d-3e804e45106c.png">

『変数の設定』で入力値を対象年に設定します。
値：`@{triggerBody()?['number']}`

---
### ③-4) PowerApp または Flow に応答する(空値)
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/e33cd019-8ad3-907b-2851-98ee83883400.png">

『PowerApp または Flow に応答する』で休日配列を空配列で設定します。
* 休日配列：`[]`

---
### ③-5) 終了：年指定エラー
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/a71d4366-6fbf-3f0e-f2f4-241c829de802.png">

『終了』で処理を止めます。

---
# ④固定日祝日(5月)スコープ
固定日祝日が3日連続する5/3～5を切り出してスコープとします。
構成は以下の通りです。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/f6ce9f99-caa0-3b7d-47da-3c3ce69c877c.png">

---
### ④-1～3）憲法記念日/みどりの日/こどもの日追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/0e929891-f5e3-d66e-8d7e-4a0ea5e602a8.png">

『配列変数に追加』で休日配列に3つの祝日を追加します。
(憲法記念日追加)値：`@{variables('対象年')}-05-03`
(みどりの日追加)値：`@{variables('対象年')}-05-04`
(こどもの日追加)値：`@{variables('対象年')}-05-05`

---
### ④-4）ゴールデンウィーク振替休日あり？
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/9b829230-6b5c-6f1f-c45b-29e74d3c31f6.png">

『条件』で5/5の曜日から振替休日有無を判定します。
* 左辺： (式)`dayOfWeek(concat(variables('対象年'),'-05-05'))​` 
* 条件：次の値以下
* 右辺： 2

---
### ④-5）祝日配列にゴールデンウイーク振替休日追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/f2715331-aca9-0b24-9d4d-fb4b36488d4e.png">

『配列変数に追加』で振替休日を追加します。
値：`@{variables('対象年')}-05-06`

---
# ⑤固定日祝日(5月除く)スコープ
5月以外の固定日祝日（7つ）を切り出してスコープとします。
* 元旦
* 建国記念の日
* 天皇誕生日
* 昭和の日
* 山の日
* 文化の日
* 勤労感謝の日

構成は以下の通りです。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/70622c12-de75-648f-70c5-484d999ef3a4.png">

途中省略していますが、7つの祝日で同様の処理を繰り返します。

:::note warn
実装当初はJSONデータに祝日を埋めてループ処理したのですが、処理時間がかかったため処理を分離しました。個別処理に変更したことで、処理時間は3分の1以下に減ってます。
:::

---
### ⑤-1）祝日追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/aab43a8a-631e-af66-51c4-3de40900efd7.png">

『配列変数に追加』で休日配列に祝日を追加します。
* (元旦)値：`@{variables('対象年')}-01-01`
* (建国記念の日)値：`@{variables('対象年')}-02-11`
* (天皇誕生日)値：`@{variables('対象年')}-02-23`
* (昭和の日)値：`@{variables('対象年')}-04-29`
* (山の日)値：`@{variables('対象年')}-08-11`
* (文化の日)値：`@{variables('対象年')}-11-03`
* (勤労感謝の日)値：`@{variables('対象年')}-11-23`
---
### ⑤-2）祝日＝日曜？
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/6b478d48-2acf-d459-64cc-99fb765c749f.png">

『条件』で該当祝日が日曜日か判定します。
* 左辺：(式)`dayOfWeek(last(variables('休日配列')))` 
* 条件：次の値以下
* 右辺： 0

---
### ⑤-3）祝日：振替休日追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/8a763be6-dad6-b36b-6f44-bf1b6841e3b4.png">

『配列変数に追加』で休日配列に振替休日を追加します。
* (元旦)値：`@{variables('対象年')}-01-02`
* (建国記念の日)値：`@{variables('対象年')}-02-12`
* (天皇誕生日)値：`@{variables('対象年')}-02-24`
* (昭和の日)値：`@{variables('対象年')}-04-30`
* (山の日)値：`@{variables('対象年')}-08-12`
* (文化の日)値：`@{variables('対象年')}-11-04`
* (勤労感謝の日)値：`@{variables('対象年')}-11-24`

---
# ⑥春分秋分スコープ
春分の日・秋分の日を切り出してスコープとします。
構成は以下の通りです。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/d92204b7-3b5a-8dbb-1a16-414121545e6e.png">

---
### ⑥-1）春分の日作成
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/136d1085-5829-69f7-067b-0d340dd3a985.png">

『作成』で春分の日を計算して設定します。
値：`@{concat(variables('対象年'),'-03-',string(sub(int(split(string(add(20.8431,mul(0.242194,sub(variables('対象年'),1980)))),'.')[0]),int(split(string(div(sub(variables('対象年'),1980),4)),'.')[0]))))}`

:::note warn
振替休日は固定日のように特定日付で指定できず、last＋addDaysで休日配列から値取得＋計算して再格納すると自己参照エラーとなってしまうため、値定義を切り出します。
:::

---
### ⑥-2）春分の日追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/d583bc13-9156-8a65-0170-5051abd52950.png">

『配列変数に追加』で休日配列に春分の日を追加します。
値：`@{outputs('春分の日作成')}`

---
### ⑥-3）春分の日＝日曜？
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/71a87df7-ef28-5eab-1f6c-4d8b69ca748b.png">

『条件』で春分の日が日曜日か判定します。
* 左辺：(式)`dayOfWeek(last(variables('休日配列')))` 
* 条件：次の値以下
* 右辺： 0

---
### ⑥-4）春分の日：振替休日追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/215d7fbd-1bb5-0741-01ba-c433e0ddef0b.png">

『配列変数に追加』で休日配列に振替休日を追加します。
* 値：`@{addDays(outputs('春分の日作成'),1,'yyyy-MM-dd')}`

---
### ⑥-5）秋分の日作成
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/1cec32cb-f879-8f1e-dcc2-6cc70e8263e0.png">

『作成』で秋分の日を計算して設定します。
値：`@{concat(variables('対象年'),'-09-',string(sub(int(split(string(add(23.2488,mul(0.242194,sub(variables('対象年'),1980)))),'.')[0]),int(split(string(div(sub(variables('対象年'),1980),4)),'.')[0]))))}`

---
### ⑥-6～8）
⑥-2～4と同様の処理を行います。

---
### ⑥-9）シルバーウィーク国民の祝日？
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/8c07ba1b-0e11-9512-012c-ce5d54455b2f.png">

秋分の日は9/22-24、敬老の日は9月第3月曜（9/15～21）となるため、秋分の日が水曜の場合、間に挟まれた平日は国民の祝日になります。『条件』で秋分の日が水曜日か判定します。
* 左辺：(式)`dayOfWeek(last(variables('休日配列')))` 
* 条件：次の値に等しい
* 右辺： 3

---
### ⑥-10）シルバーウィーク：国民の祝日追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/060123c3-c6ef-09f9-e2c2-38b46ca15af0.png">

『配列変数に追加』で休日配列に国民の祝日を追加します。
* 値：`@{addDays(outputs('秋分の日作成'),-1,'yyyy-MM-dd')}`

---
# ⑦ハッピーマンデースコープ
ハッピーマンデー祝日（4つ）を切り出してスコープとします。
* 成人の日
* 海の日
* 敬老の日
* スポーツの日
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/903ac80e-f0fc-6bf5-7df2-ea474f59751e.png">

『配列変数に追加』で休日配列に祝日を追加します。
* (成人の日)値：`@{concat(variables('対象年'),'-01-',formatNumber(sub(14,dayOfWeek(concat(variables('対象年'),'-01-06'))),'00'))}`
* (海の日)値：`@{concat(variables('対象年'),'-07-',formatNumber(sub(21,dayOfWeek(concat(variables('対象年'),'-07-06'))),'00'))}`
* (敬老の日)値：`@{concat(variables('対象年'),'-09-',formatNumber(sub(21,dayOfWeek(concat(variables('対象年'),'-09-06'))),'00'))}`
* (スポーツの日)値：`@{concat(variables('対象年'),'-10-',formatNumber(sub(14,dayOfWeek(concat(variables('対象年'),'-10-06'))),'00'))}`

ハッピーマンデーは月曜固定なので振替休日の考慮は不要です。

---
# ⑧会社休暇スコープ
自社の年末年始休暇は12/29～1/3なので、元旦、元旦振替休日と重複しないよう休日追加します。
構成は以下の通りです。
<img width="1200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/129d06be-00ef-c643-591a-c71ee7b1e4f6.png">

---
### ⑧-1）会社休暇含む？
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/bed525bf-0672-90f3-43ee-05172217e998.png">

『条件』で引数が会社休暇含むを選択しているか判定します。
* 左辺：(式)`triggerBody()?['boolean']` 
* 条件：次の値に等しい
* 右辺：true

---
### ⑧-2）1月2日データ作成
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/9d10a19d-e741-7e7d-3906-691a8f3b65e5.png">

『作成』で1/2を計算して設定します。
値：`@{variables('対象年')}-01-02`

---
### ⑧-3）1月2日＝月曜？
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/44c58ceb-ef12-945b-4659-394319beb91d.png">

『条件』で1/2が月曜日（振替休日）か判定します。
* 左辺：(式)`dayOfWeek(outputs('1月2日データ作成'))` 
* 条件：次の値に等しい
* 右辺：1

---
### ⑧-4）1月2日追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/07ac52a1-286d-e6ba-9a3c-8c6749cfe5d8.png">

『配列変数に追加』で休日配列に1/2を追加します。
* 値：`@{outputs('1月2日データ作成')}`

---
### ⑧-5～8）1/3、12/29～31追加
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/be126b4b-6588-9735-733f-e03d62333793.png">

『配列変数に追加』で休日配列に1/3、12/29～31を追加します。
* (1/3)値：`@{variables('対象年')}-01-03`
* (12/29)値：`@{variables('対象年')}-12-29`
* (12/30)値：`@{variables('対象年')}-12-30`
* (12/31)値：`@{variables('対象年')}-12-31`

---
# ⑨PowerApp または Flow に応答する
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681410/32670257-6817-6a39-b473-ab56e54b5137.png">

『PowerApp または Flow に応答する』で作成した休日配列をソート・JSON文字列化して返り値として設定します。
* 休日配列：`@concat('["',join(sort(variables('休日配列')),'","'),'"]')`

:::note warn
休日配列は応答値に配列型設定できないため、配列形式の文字列で設定します。親フロー側で配列として扱う場合はjson関数で変換する必要があります。
:::