PowerShellで1949年以降の休日を取得する

たしか先月で、GoogleのカレンダーAPI、v1/v2が使えなくなりました。
ということでGoogle依存を解消したPowerShellでの休日取得を考えてみました。

追記 2021/10/12
2021年東京オリンピック周りの変更を反映
2007以降のみを削除
関数名等修正

過去の記事と今回の方針

UWSCで祝日を算出する - じゅんじゅんのきまぐれ
UWSCで1949年以降の祝日を算出する - じゅんじゅんのきまぐれ
javascriptで休日判定 - じゅんじゅんのきまぐれ
と(PowerShellではないですが)書いてきました。


また、完全にハードコードしてしまえ、という思想の人もいるのですね。
holiday_jp(Ruby gem) のPHP版、JavaScript版を作りました - Copy/Cut/Paste/Hatena
これはこれでアリかと思います。
ハードコード版も作ってみましょうか。

オフライン版

完全ハードコードの人のと答え合わせしてあります。

function Get-HolidayJp() {
	Param ([int]$Year = [DateTime]::Now.Year)

	# 注意! 1948年以前と2100年以降は未対応
	if($Year -le 1948 -or $Year -ge 2100) { return @() }

	# オブジェクト用意
	$holidayObjs = @{
		元日=@{ name='元日'; nameEn='New Year''s Day' }
		成人=@{ name='成人の日'; nameEn='Coming of Age Day' }
		建国=@{ name='建国記念の日'; nameEn='National Foundation Day' }
		春分=@{ name='春分の日'; nameEn='Vernal Equinox Day' }
		憲法=@{ name='憲法記念日'; nameEn='Constitution Memorial Day' }
		みど=@{ name='みどりの日'; nameEn='Greenery Day' }
		子供=@{ name='こどもの日'; nameEn='Children''s Day' }
		海の=@{ name='海の日'; nameEn='Marine Day' }
		山の=@{ name='山の日'; nameEn='Mountain Day' }
		敬老=@{ name='敬老の日'; nameEn='Respect for the Aged Day' }
		秋分=@{ name='秋分の日'; nameEn='Autumnal Equinox Day' }
		体育=@{ name='体育の日'; nameEn='Health and Sports Day' }
		スポ=@{ name='スポーツの日'; nameEn='Health and Sports Day' }
		文化=@{ name='文化の日'; nameEn='National Culture Day' }
		勤労=@{ name='勤労感謝の日'; nameEn='Labor Thanksgiving Day' }
		天皇=@{ name='天皇誕生日'; nameEn='Emperor''s Birthday' }
		昭和=@{ name='昭和の日'; nameEn='Showa Day' }
		振替=@{ name='振替休日'; nameEn='Holiday in lieu' }
		#国民=@{ name='国民の休日'; nameEn='Citizen''s Holiday' } オンラインにあわせる
		国民=@{ name='休日'; nameEn='Holiday by law' }
		明仁結婚=@{ name='皇太子明仁親王の結婚の儀'; nameEn='The Rite of Wedding of HIH Crown Prince Akihito' }
		大喪=@{ name='昭和天皇の大喪の礼'; nameEn='The Funeral Ceremony of Emperor Showa.' }
		即位=@{ name='即位礼正殿の儀'; nameEn='The Ceremony of the Enthronement of His Majesty th Emperor (at the Seiden)' }
		徳仁結婚=@{ name='皇太子徳仁親王の結婚の儀'; nameEn='The Rite of Wedding of HIH Crown Prince Naruhito' }
	}

	# 特別組
	$ret = New-Object System.Collections.SortedList
	if($Year -eq 1959) { $ret[[DateTime]"$Year/04/10"] = $holidayObjs['明仁結婚'] }
	if($Year -eq 1989) { $ret[[DateTime]"$Year/02/24"] = $holidayObjs['大喪'] }
	if($Year -eq 1990) { $ret[[DateTime]"$Year/11/12"] = $holidayObjs['即位'] }
	if($Year -eq 1993) { $ret[[DateTime]"$Year/06/09"] = $holidayObjs['徳仁結婚'] }
	if($Year -eq 2019) {
		$ret[[DateTime]"$Year/05/01"] = $holidayObjs['国民']	# 令和天皇の即位
		$ret[[DateTime]"$Year/10/22"] = $holidayObjs['国民']	# 令和天皇の即位礼正殿の儀
	}

	# 固定組
	$ret[[DateTime]"$Year/01/01"] = $holidayObjs['元日']
	$ret[[DateTime]"$Year/05/03"] = $holidayObjs['憲法']	# 憲法記念日 1948年から
	$ret[[DateTime]"$Year/05/05"] = $holidayObjs['子供']
	$ret[[DateTime]"$Year/11/03"] = $holidayObjs['文化']	# 明治節
	$ret[[DateTime]"$Year/11/23"] = $holidayObjs['勤労']	# 勤労感謝の日 新嘗祭1873年からか?。勤労感謝の日としては1948年から
	$ret[[DateTime]"$Year/04/29"] = $holidayObjs['天皇']	# 天皇誕生日 昭和。年が進むと名称が変わる
	if($Year -ge 1967) { $ret[[DateTime]"$Year/02/11"] = $holidayObjs['建国'] }	# 建国記念の日 紀元節
	if($Year -ge 1989) {
		if($Year -ge 2020) {
			$ret[[DateTime]"$Year/02/23"] = $holidayObjs['天皇']	# 令和
		} elseif($Year -lt 2019) {
			$ret[[DateTime]"$Year/12/23"] = $holidayObjs['天皇']	# 平成
		}
		if($Year -ge 2007) {
			$ret[[DateTime]"$Year/04/29"] = $holidayObjs['昭和']
			$ret[[DateTime]"$Year/05/04"] = $holidayObjs['みど']
		} else {
			$ret[[DateTime]"$Year/04/29"] = $holidayObjs['みど']
		}
	}
	if($Year -ge 2016 -and $Year -ne 2020 -and $Year -ne 2021) { $ret[[DateTime]"$Year/08/11"] = $holidayObjs['山の'] }

	# 第n月曜組
	$getNthDay = {
		Param([int]$n, [DayOfWeek]$w, [DateTime]$b = [DateTime]::Now)
		$d = 1 - $b.Day
		$c = [int]$w - [int]($b.AddDays($d).DayOfWeek)
		$d += $c + ([int]($c -lt 0) + $n - 1) * 7
		return $b.AddDays($d)
	}
	if($Year -ge 2000) {
		$ret[(&$getNthDay 2 Monday "$Year/01/15")] = $holidayObjs['成人']
		if($Year -ge 2003) {
			$ret[(&$getNthDay 3 Monday "$Year/09/15")] = $holidayObjs['敬老']
			if($Year -eq 2020) {
				$ret[[DateTime]"$Year/07/23"] = $holidayObjs['海の']
				$ret[[DateTime]"$Year/07/24"] = $holidayObjs['スポ']
				$ret[[DateTime]"$Year/08/10"] = $holidayObjs['山の']
			} elseif($Year -eq 2021) {
				$ret[[DateTime]"$Year/07/22"] = $holidayObjs['海の']
				$ret[[DateTime]"$Year/07/23"] = $holidayObjs['スポ']
				$ret[[DateTime]"$Year/08/08"] = $holidayObjs['山の']
			} else {
				$ret[(&$getNthDay 3 Monday "$Year/07/20")] = $holidayObjs['海の']
				if($Year -ge 2021) {
					$ret[(&$getNthDay 2 Monday "$Year/10/10")] = $holidayObjs['スポ']
				} else {
					$ret[(&$getNthDay 2 Monday "$Year/10/10")] = $holidayObjs['体育']
				}
			}
		} else {
			$ret[[DateTime]"$Year/07/20"] = $holidayObjs['海の']
			$ret[[DateTime]"$Year/09/15"] = $holidayObjs['敬老']
			$ret[(&$getNthDay 2 Monday "$Year/10/10")] = $holidayObjs['体育']
		}
	} else {
		if($Year -ge 1949) { $ret[[DateTime]"$Year/01/15"] = $holidayObjs['成人'] }
		if($Year -ge 1966) {
			$ret[[DateTime]"$Year/09/15"] = $holidayObjs['敬老']
			$ret[[DateTime]"$Year/10/10"] = $holidayObjs['体育']
			if($Year -ge 1996) { $ret[[DateTime]"$Year/07/20"] = $holidayObjs['海の'] }
		}
	}

	# 春分の日・秋分の日(1918-2099用) 1948年の秋分の日が最初の祝日
	$ret[[DateTime]"$Year/03/$([Math]::Floor(22-(3-(($Year-1892)%4)+[Math]::Floor(($year-1892)/33))*0.25))"] = $holidayObjs['春分']
	$ret[[DateTime]"$Year/09/$([Math]::Floor(25-(3-(($Year-1820)%4)+[Math]::Floor(($year-1820)/32))*0.25))"] = $holidayObjs['秋分']

	# 振替休日 1973年から(ただし、1973-02-11は除く)
	if($Year -ge 1973) {
		$i = 0
		while($i -lt $ret.Count) {
			$t = $ret.GetKey($i)
			if($t -gt [DateTime]'1973/02/11') {
				if($t.DayOfWeek -eq 'Sunday') {
					while($ret.ContainsKey($t)) {
						$t =$t.AddDays(1)
					}
					$ret[$t] = $holidayObjs['振替']
					$i++
				}
			}
			$i++
		}
	}

	# 国民の休日 1986年から
	if($Year -gt 1985) {
		$i = 1
		while($i -lt $ret.Count) {
			$t = $ret.GetKey($i-1).AddDays(1)
			if($t -eq $ret.GetKey($i).AddDays(-1) -and $t.DayOfWeek -ne 'Sunday') {
				$ret[$t] = $holidayObjs['国民']
				$i++
			}
			$i++
		}
	}

	return $ret
}

テストコード(Get-HolidayHardCodeにも依存)

& {
	for($i = 1970; $i -lt 2051; $i++) {
		$resOff = Get-HolidayJp $i
		$resHar = Get-HolidayHardCode $i
		if($resOff.Count -ge $resHar.Count) {
			for($j = 0; $j -lt $resOff.Count; $j++) {
				$t = $resOff.GetKey($j)
				if($resOff[$t].nameEn -ne $resHar[$t].nameEn -or $resOff[$t].name -ne $resHar[$t].name) {
					'内容が違う Offline:{0:yyyy/MM/dd} {1} {2}  HardCode:{3} {4}' -f $t,$resOff[$t].nameEn,$resOff[$t].name,$resHar[$t].nameEn,$resHar[$t].name
				}
			}
		} else {
			for($j = 0; $j -lt $resHar.Count; $j++) {
				$t = $resHar.GetKey($j)
				if($resOff[$t].nameEn -ne $resHar[$t].nameEn -or $resOff[$t].name -ne $resHar[$t].name) {
					'内容が違う Offline:{0:yyyy/MM/dd} {1} {2}  HardCode:{3} {4}' -f $t,$resOff[$t].nameEn,$resOff[$t].name,$resHar[$t].nameEn,$resHar[$t].name
				}
			}
		}
	}
}

ハードコード版

holidays.ymlを読み込み式にしました。
gitから取得して適宜パスを修正してください。
https://github.com/holiday-jp/holiday_jp

function Get-HolidayHardCodeAll() {

	# オブジェクト用意
	$holidayObjs = @{
		元日=@{ name='元日'; nameEn='New Year''s Day' };
		成人の日=@{ name='成人の日'; nameEn='Coming of Age Day' };
		建国記念の日=@{ name='建国記念の日'; nameEn='National Foundation Day' };
		春分の日=@{ name='春分の日'; nameEn='Vernal Equinox Day' };
		憲法記念日=@{ name='憲法記念日'; nameEn='Constitution Memorial Day' };
		みどりの日=@{ name='みどりの日'; nameEn='Greenery Day' };
		こどもの日=@{ name='こどもの日'; nameEn='Children''s Day' };
		海の日=@{ name='海の日'; nameEn='Marine Day' };
		山の日=@{ name='山の日'; nameEn='Mountain Day' };
		敬老の日=@{ name='敬老の日'; nameEn='Respect for the Aged Day' };
		秋分の日=@{ name='秋分の日'; nameEn='Autumnal Equinox Day' };
		体育の日=@{ name='体育の日'; nameEn='Health and Sports Day' };
		スポーツの日=@{ name='スポーツの日'; nameEn='Health and Sports Day' };
		文化の日=@{ name='文化の日'; nameEn='National Culture Day' };
		勤労感謝の日=@{ name='勤労感謝の日'; nameEn='Labor Thanksgiving Day' };
		天皇誕生日=@{ name='天皇誕生日'; nameEn='Emperor''s Birthday' };
		昭和の日=@{ name='昭和の日'; nameEn='Showa Day' };
		振替休日=@{ name='振替休日'; nameEn='Holiday in lieu' };
		#国民の休日=@{ name='国民の休日'; nameEn='Citizen''s Holiday' } オンラインにあわせる
		休日=@{ name='休日'; nameEn='Holiday by law' };
		大喪の礼=@{ name='昭和天皇の大喪の礼'; nameEn='The Funeral Ceremony of Emperor Showa.' };
		即位礼正殿の儀=@{ name='即位礼正殿の儀'; nameEn='The Ceremony of the Enthronement of His Majesty th Emperor (at the Seiden)' };
		結婚の儀=@{ name='皇太子徳仁親王の結婚の儀'; nameEn='The Rite of Wedding of HIH Crown Prince Naruhito' };
	}

	$ret = @{}
	$data = (Get-Content ../../holiday_jp/holidays.yml -Encoding UTF8)
	$data.Split("`n") | % {
		$tmp = $_ -replace '(.*)',''
		$tmp = $tmp -split ':'
		if($tmp.Length -gt 1) {
			$wk = $tmp[1].Trim() -split ' '
			if($wk.Length -gt 1) { $tmp[1] = $wk[1] }
			$ret[[DateTime]$tmp[0]] = $holidayObjs[$tmp[1].Trim()]
		}
	}

	return $ret
}


function Get-HolidayHardCode() {
	Param ([int]$Year = [DateTime]::Now.Year)

	# 注意! 1970年より前と2051年以降は未対応
	if($Year -lt 1970 -or $Year -ge 2051) { return @() }

	$ret = New-Object System.Collections.SortedList
	$res = Get-HolidayHardCodeAll
	foreach($t in $res.Keys) {
		if($t.Year -eq $Year) {
			$ret[$t] = $res[$t]
		} else {
			continue
		}
	}

	return $ret
}