UWSCで1949年以降の祝日を算出する

現在の祝日は、以下がシンプルでおすすめ。
UWSCで祝日を算出する - じゅんじゅんのきまぐれ


過去も気になる人向けに考えてみた。
Googleカレンダー系も結構人手メンテが必要なようなので、算出できるのは有効かも。
こっちの今後のメンテは、私の気分次第。
1949年以降なら大体あっていると思われる。(なんとなくはあっている。適当)

追記 2021/10/12
2021年東京オリンピック周りの変更を追加。

追記 2018/06/25
2020年東京オリンピック周りの変更を追加。
機能しなくなったオンライン系をばっさり削除。

追記 2014/05/27
「山の日」追加

スクリプト

Holiday.uws

OPTION EXPLICIT

Holiday.CheckUpdated(PARAM_STR)

IFB LENGTH(PARAM_STR) > 1 THEN
	DIM holiday
	FOR holiday in Holiday.GetYear(PARAM_STR[1])
		PRINT holiday[0] + " " + holiday[1]
	NEXT
ELSEIF GET_UWSC_NAME = "Holiday.uws" THEN
	DIM ret = 0, i, holidays, f, hs

	holidays = INPUT("holidays.ymlをDrag&Dropしてください")
	IFB LENGTH(holidays) = 0 THEN
		ret = ret + 1
	ELSE
		f = FOPEN(holidays)
		IFB f = -1 THEN
			ret = ret + 1
		ELSE
			holidays = SPLIT(FGET(f, F_ALLTEXT), "<#CR>")
			FCLOSE(f)
		ENDIF
	ENDIF
	IFB ret = 0 AND LENGTH(holidays) > 0 THEN
		FOR i = 1 TO LENGTH(holidays) - 1	// 1行目はSkip
			holidays[i-1] = SPLIT(holidays[i], ": ")
		NEXT
		GETTIME(1, holidays[LENGTH(holidays)-2][0])
		hs = Holiday.Get(holidays[0][0], Holiday.MakeDate(G_TIME_YY, G_TIME_MM, G_TIME_DD), 5000)
	ENDIF
	f = LENGTH(holidays) - 1
	IFB ret = 0 AND f > 0 THEN
		IF LENGTH(hs) < f THEN f = LENGTH(hs)
		FOR i = 0 TO f - 1
			IFB hs[i][0] <> holidays[i][0] OR POS(hs[i][1], holidays[i][1]) = 0 THEN
				PRINT "不一致 Module:" + hs[i][0] + hs[i][1] + " HardCode:" + holidays[i][0] + holidays[i][1]
				ret = ret + 1
			ENDIF
		NEXT
		IFB LENGTH(holidays) <= LENGTH(hs) THEN
			FOR i = f TO LENGTH(hs) - 1
				PRINT "不一致 Module:" + hs[i][0] + hs[i][1] + " HardCode:なし"
				ret = ret + 1
			NEXT
		ELSE
			FOR i = f TO LENGTH(holidays) - 2
				PRINT "不一致 Module:なし HardCode:" + holidays[i][0] + holidays[i][1]
				ret = ret + 1
			NEXT
		ENDIF
	ENDIF

	IFB ret > 0 THEN
		MSGBOX("試験NG:" + ret)
	ELSE
		MSGBOX("試験OK")
	ENDIF
ENDIF


MODULE Holiday

	FUNCTION CheckUpdated(path[], bPrint=TRUE)
		GETTIME()
		DIM p = "Holiday.uws", sc = CREATEOLEOBJ("Scripting.FileSystemObject"), y = G_TIME_YY, ms, i, dlm
		IF LENGTH(path) THEN p = path[0]
		RESULT = sc.FileExists(p)
		IFB RESULT THEN
			dlm = sc.GetFile(p).DateLastModified
			WITH CREATEOLEOBJ("VBScript.RegExp")
				.Pattern = "(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)"
				ms = .Execute(sc.GetFile(p).DateLastModified)
				IFB ms.Count > 0 THEN
					dlm = ""
					FOR i = 0 TO ms.Item(0).SubMatches.Count - 1
						IF LENGTH(ms.Item(0).SubMatches(i)) < 2 THEN dlm = dlm + "0"
						dlm = dlm + ms.Item(0).SubMatches(i)
					NEXT
				ENDIF
			ENDWITH
			GETTIME(0, dlm)
			RESULT = (G_TIME_YY >= y)
			IF !RESULT AND bPrint THEN PRINT "モジュールが古いかもしれません。祝日判定条件の見直しをお願いします"
		ELSE
			IF bPrint THEN PRINT "更新チェックができません。CALL文を修正し第一引数にパスを指定してください"
		ENDIF
	FEND

	FUNCTION MakeDate(y, m, d)
		IF VAL(y) < 100 THEN y = y + 2000
		IF VAL(m) < 10 THEN m = "0" + m
		IF VAL(d) < 10 THEN d = "0" + d
		RESULT = y + "-" + m + "-" + d
	FEND

	FUNCTION GetNthDay(n, w, b=EMPTY)
		IF b <> EMPTY THEN GETTIME(0, b)
		DIM d = 1 - G_TIME_DD
		GETTIME(d, b)	// 月初日の曜日取得
		w = w - G_TIME_WW
		d = d + w + ((w < 0) + n - 1) * 7	// 第何分だけずらす
		GETTIME(d, b)
		RESULT = G_TIME_YY4 + "-" + G_TIME_MM2 + "-" + G_TIME_DD2
	FEND
	FUNCTION GetYear(y=EMPTY)
		// 注意!
		// 1948年以前と2100年以降は未対応。2014年以降は修正が必要かも
		IFB y = EMPTY THEN
			GETTIME()
			y = G_TIME_YY
		ENDIF
		IFB y < 1949 OR y > 2099 THEN
			RESULT = SAFEARRAY(0, -1)
			EXIT
		ENDIF

		HASHTBL tbl = HASH_SORT

		// 特別組
		IF y = 1959 THEN tbl[y+"-04-10"] = "皇太子明仁親王の結婚の儀"
		IF y = 1989 THEN tbl[y+"-02-24"] = "大喪の礼"	// 昭和天皇の大喪の礼
		IF y = 1990 THEN tbl[y+"-11-12"] = "即位礼正殿の儀"
		IF y = 1993 THEN tbl[y+"-06-09"] = "結婚の儀"	// 皇太子徳仁親王の結婚の儀
		IFB y = 2019 THEN
			tbl[y+"-05-01"] = "休日"	// 令和天皇の即位
			tbl[y+"-10-22"] = "休日"	// 令和天皇の即位礼正殿の儀
		ENDIF

		// 固定組
		tbl[y+"-01-01"] = "元日"
		tbl[y+"-05-03"] = "憲法記念日"		// 1948年から
		tbl[y+"-05-05"] = "こどもの日"
		tbl[y+"-11-03"] = "文化の日"		// 明治節
		tbl[y+"-11-23"] = "勤労感謝の日"	// 新嘗祭。1873年からか?。勤労感謝の日としては1948年から
		tbl[y+"-04-29"] = "天皇誕生日"		// 昭和。年が進むと名称が変わる
		IF y >= 1967 THEN tbl[y+"-02-11"] = "建国記念の日"	// 紀元節
		IFB y >= 2007 THEN
			tbl[y+"-04-29"] = "昭和の日"
			tbl[y+"-05-04"] = "みどりの日"
			IFB y >= 2020 THEN
				tbl[y+"-02-23"] = "天皇誕生日"		// 令和
			ELSEIF y < 2019 THEN
				tbl[y+"-12-23"] = "天皇誕生日"		// 平成
			ENDIF
		ELSEIF y >= 1989 THEN
			tbl[y+"-04-29"] = "みどりの日"
			tbl[y+"-12-23"] = "天皇誕生日"		// 平成
		ENDIF
		IF y >= 2016 AND y <> 2020 AND y <> 2021 THEN tbl[y+"-08-11"] = "山の日"

		// 第n月曜組
		IF y >= 2000 THEN
			tbl[GetNthDay(2, 1, y + "-01-15")] = "成人の日"
			IFB y >= 2003 THEN
				tbl[GetNthDay(3, 1, y + "-09-15")] = "敬老の日"
				IFB y = 2020 THEN
					tbl[y+"-07-23"] = "海の日"
					tbl[y+"-07-24"] = "スポーツの日"
					tbl[y+"-08-10"] = "山の日"
				ELSEIF y = 2021 THEN
					tbl[y+"-07-22"] = "海の日"
					tbl[y+"-07-23"] = "スポーツの日"
					tbl[y+"-08-08"] = "山の日"
				ELSE
					tbl[GetNthDay(3, 1, y + "-07-20")] = "海の日"
					IF y > 2020 THEN
						tbl[GetNthDay(2, 1, y + "-10-10")] = "スポーツの日"
					ELSE
						tbl[GetNthDay(2, 1, y + "-10-10")] = "体育の日"
					ENDIF
				ENDIF
			ELSE
				tbl[y+"-07-20"] = "海の日"
				tbl[y+"-09-15"] = "敬老の日"
				tbl[GetNthDay(2, 1, y + "-10-10")] = "体育の日"
			ENDIF
		ELSE
			IF y >= 1949 THEN tbl[y+"-01-15"] = "成人の日"
			IFB y >= 1966 THEN
				tbl[y+"-09-15"] = "敬老の日"
				tbl[y+"-10-10"] = "体育の日"
				IF y >= 1996 THEN tbl[y+"-07-20"] = "海の日"
			ENDIF
		ENDIF

		// 春分の日・秋分の日(1918-2099用) 1948年の秋分の日が最初の祝日
		tbl[y+"-03-"+INT(22-(3-((y-1892) MOD 4)+INT((y-1892)/33))*0.25)] = "春分の日"
		tbl[y+"-09-"+INT(25-(3-((y-1820) MOD 4)+INT((y-1820)/32))*0.25)] = "秋分の日"

		// 振替休日 1973年から(ただし、1973-02-11は除く)
		DIM i = 0, b
		IFB y >= 1973 THEN
			WHILE i < LENGTH(tbl)
				IFB GETTIME(1, tbl[i, HASH_KEY]) > -848361600 THEN
					b = G_TIME_YY4 + "-" + G_TIME_MM2 + "-" + G_TIME_DD2
					IFB G_TIME_WW = 1 THEN
						WHILE tbl[b, HASH_EXISTS]
							GETTIME(1, b)
							b = G_TIME_YY4 + "-" + G_TIME_MM2 + "-" + G_TIME_DD2
						WEND
						tbl[b] = "振替休日"
						i = i + 1
					ENDIF
				ENDIF
				i = i + 1
			WEND
		ENDIF

		// 国民の休日 1986年から
		IFB y > 1985 THEN
			i = 1
			WHILE i < LENGTH(tbl)
				IFB GETTIME(1, tbl[i-1, HASH_KEY]) = GETTIME(-1, tbl[i, HASH_KEY]) AND G_TIME_WW <> 0 THEN
					tbl[G_TIME_YY4+"-"+G_TIME_MM2+"-"+G_TIME_DD2] = "休日"	// 国民の休日
					i = i + 1
				ENDIF
				i = i + 1
			WEND
		ENDIF

		// 結果格納
		RESULT = SAFEARRAY(0, LENGTH(tbl) - 1)
		FOR i = 0 TO LENGTH(tbl) - 1
			RESULT[i] = SAFEARRAY(0, 1)
			RESULT[i][0] = tbl[i, HASH_KEY]
			RESULT[i][1] = tbl[i, HASH_VAL]
		NEXT
	FEND
	FUNCTION Get(sd=EMPTY, ed=EMPTY, num=50)
		DIM sdt = GETTIME(0, sd), sdy = G_TIME_YY
		DIM edt = GETTIME(0, ed), edy = G_TIME_YY
		DIM res = SAFEARRAY(0, edy - sdy), i, si, ei, ni, j, k, r
		FOR i = sdy TO edy
			res[i-sdy] = GetYear(i)
		NEXT
		sdy = edy - sdy
		FOR i = 0 TO sdy
			r = res[i]
			SELECT i
			CASE 0
				// 開始位置検索
				si = 0
				WHILE (GETTIME(0, r[si][0]) < sdt)
					si = si + 1
					IF si >= LENGTH(r) THEN BREAK
				WEND
				ni = LENGTH(r) - si
			CASE sdy
				// 終了位置検索
				ei = 0
				WHILE (GETTIME(0, r[ei][0]) < edt)
					ei = ei + 1
					IF ei >= LENGTH(r) THEN BREAK
				WEND
				ni = ni + ei
			DEFAULT
				ni = ni + LENGTH(r)
			SELEND
		NEXT
		// 結果の再格納
		RESULT = SAFEARRAY(0, ni - 1)
		k = 0
		FOR i = 0 TO sdy
			r = res[i]
			edy = LENGTH(r) - 1
			IF i = sdy THEN edy = ei - 1
			FOR j = si TO edy
				RESULT[k] = SAFEARRAY(0, 1)
				RESULT[k][0] = r[j][0]
				RESULT[k][1] = r[j][1]
				k = k + 1
				IF k >= num THEN BREAK 2
			NEXT
			si = 0
		NEXT
	FEND

ENDMODULE


算出という意味で必要なのは、Get/GetYear/GetNthDay関数。
他は、テストコードだったり。
ただし、何気に重要なのは、CheckUpdated関数。
スクリプト冒頭に

Holiday.CheckUpdated(PARAM_STR)

と書いてあり、ファイルの更新日付の年が現在年より古い場合、警告をPrintします。
地に書いてあるので、CALLでも警告します。
毎年一回、チェックを促すために存在します。
チェックのため、違うディレクトリーにあるHolidayモジュールをCallする場合、第一引数にパスを指定してください。(同じ場合は省略可)

CALL ..\lib\Holiday("..\lib\Holiday.uws")

DRY信者的には気持ち悪いですが、他に方法を思いつきませんでした。


1948年の祝日の施行は中途半端なので、1949年からとしました。
その年限りの祝日を特別組として追加。(忘れてた)


春分の日秋分の日は、Wikipediaの判定文を数式に直して一部の誤差を断念して1918年からに対応した。
本関数の対象が1949年からなので充分かと。
いずれにしても、、、間違う可能性は否定できない(今のところは正しいけどね!)
テストコードに書いたのは、ハードコードしている人のymlファイルを利用。
https://github.com/holiday-jp/holiday_jp/blob/master/holidays.yml