JavaScript で作ったものを Python で実装しました。
JHDate クラス
JHDate はオブジェクトの生成時に休日を取得しています。
datetime.date に以下を追加しています。
プロパティ:
holiday
休日名(振替休日も対象)
クラス メソッド:
holidays(year, month=None)
指定された年(year)と月(month)から祝日の一覧を返す。
振替休日は含みません。
jholiday.py
#!/usr/bin/env python3
import datetime
class JHoliday:
MONTH = {n+1: {} for n in range(12)}
def __init__(self, name, month, day, start=None, end=None):
self._name = name
self._month = month
self._day = day
self._start = start
self._end = end
mtab = JHoliday.MONTH[month]
if day not in mtab:
mtab[day] = []
mtab[day].append(self)
@property
def name(self):
return self._name
@property
def month(self):
return self._month
@property
def day(self):
return self._day
@property
def start(self):
return self._start or 1948
@property
def end(self):
return self._end or 10000
def __repr__(self):
return ('JHoliday('
f'name={repr(self.name)},'
f' month={self.month},'
f' day={repr(self.day)},'
f' start={self._start},'
f' end={self._end})')
def valid(self, year):
return self.start <= year and year <= self.end
@staticmethod
def monthtable(month):
return JHoliday.MONTH.get(month)
@staticmethod
def basic(date):
S = JHoliday
year = date.year
month = date.month
mday = date.day
wday = date.isoweekday()
mtab = S.MONTH.get(month)
dtab = mtab.get(mday)
if dtab:
ytab = list(filter(lambda h: h.valid(year), dtab))
if ytab:
return ytab[0].name
if wday == 1:
mweek = (mday - 1) // 7
monday = mtab.get(f'M{mweek+1}')
if monday:
t = list(filter(lambda h: h.valid(year), monday))
if t:
return t[0].name
t = mtab.get('VE')
if t:
ve = S.vernalequinox(year)
if ve == date:
return t[0].name
t = mtab.get('AE')
if t:
ae = S.autumnequinox(year)
if ae == date:
return t[0].name
return None
@staticmethod
def vernalequinox(year):
sec = (315569255616 * (year - 1970) + 68619916800) // 10000
return datetime.date.fromtimestamp(sec)
@staticmethod
def autumnequinox(year):
sec = (315569255616 * (year - 1970) + 229674407040) // 10000
return datetime.date.fromtimestamp(sec)
WEEK = {}
@staticmethod
def week(date):
S = JHoliday
D = datetime.date
T = datetime.timedelta
year = date.year
month = date.month
mday = date.day
if year < 1948:
return [None] * 8
wtop = D(year, month, 1).isoweekday()
mweek = (wtop + mday - 1) // 7
index = (year * 12 + month) * 6 + mweek
cache = S.WEEK.get(index)
if cache:
return cache
wday = date.isoweekday()
line = [S.basic(date + T(i - wday)) for i in range(8)]
if year >= 1973 and line[0]:
for i in range(1, 7):
if line[i] is None:
line[i] = "休日"
break
if year >= 1986:
for i in range(1, 7):
if not line[i] and line[i - 1] and line[i + 1]:
line[i] = "休日"
S.WEEK[index] = line
return line
@staticmethod
def holiday(date):
return JHoliday.week(date)[date.isoweekday()]
@staticmethod
def holidays(year, month=None):
if month is None:
month = list(range(1, 12+1))
elif isinstance(month, int):
month = [month]
r = []
for mon in sorted(month):
mtab = JHoliday.MONTH.get(mon)
if not mtab:
continue
for day in mtab:
if not list(filter(lambda h: h.valid(year), mtab[day])):
continue
if not isinstance(day, str):
r.append(datetime.date(year, mon, day))
continue
if day == 'VE':
r.append(JHoliday.vernalequinox(year))
elif day == 'AE':
r.append(JHoliday.autumnequinox(year))
elif day[:1] == 'M':
m1 = datetime.date(year, mon, 1)
mday = 1 + (7 - m1.weekday()) % 7 + (int(day[1:]) - 1) * 7
r.append(datetime.date(year, mon, mday))
return r
class JHDate:
HOLIDAYS = list(JHoliday(*p) for p in (
('元日', 1, 1),
('成人の日', 1, 15, 0, 1999),
('成人の日', 1, 'M2', 2000),
('建国記念の日', 2, 11),
('春分の日', 3, 'VE'),
('憲法記念日', 5, 3),
('みどりの日', 5, 4, 2007),
('こどもの日', 5, 5),
('海の日', 7, 20, 1996, 2002),
('海の日', 7, 'M3', 2003),
('山の日', 8, 11, 2016),
('敬老の日', 9, 15, 1966, 2002),
('敬老の日', 9, 'M3', 2003),
('秋分の日', 9, 'AE'),
('体育の日', 10, 10, 0, 1999),
('体育の日', 10, 'M2', 2000, 2019),
('スポーツの日', 10, 'M2', 2020),
('文化の日', 11, 3),
('勤労感謝の日', 11, 23),
# 昭和
('天皇誕生日', 4, 29, 0, 1988),
('みどりの日', 4, 29, 1989, 2006),
('昭和の日', 4, 29, 2007),
# 平成
('天皇誕生日', 12, 23, 1989, 2018),
# 令和
('天皇の即位の日', 5, 1, 2019, 2019),
('即位礼正殿の儀が行われる日', 10, 22, 2019, 2019),
('天皇誕生日', 2, 23, 2020),
))
def __init__(self, *args):
if len(args) == 1 and isinstance(args[0], datetime.date):
date = args[0]
elif len(args) == 3:
date = datetime.date(*args)
else:
raise TypeError(*args)
self._date = date
self._holiday = JHoliday.holiday(date) or ''
@property
def min(self):
return self._date.min
@property
def max(self):
return self._date.max
@property
def resolution(self):
return self._date.resolution
@property
def year(self):
return self._date.year
@property
def month(self):
return self._date.month
@property
def day(self):
return self._date.day
@property
def holiday(self):
return self._holiday
def __repr__(self):
return f'JHDate({self.year}, {self.month}, {self.day}, {repr(self.holiday)})'
def __lt__(self, rhs): return self._date.__lt__(rhs._date if isinstance(rhs, JHDate) else rhs)
def __le__(self, rhs): return self._date.__le__(rhs._date if isinstance(rhs, JHDate) else rhs)
def __eq__(self, rhs): return self._date.__eq__(rhs._date if isinstance(rhs, JHDate) else rhs)
def __ne__(self, rhs): return self._date.__ne__(rhs._date if isinstance(rhs, JHDate) else rhs)
def __ge__(self, rhs): return self._date.__ge__(rhs._date if isinstance(rhs, JHDate) else rhs)
def __gt__(self, rhs): return self._date.__gt__(rhs._date if isinstance(rhs, JHDate) else rhs)
def __add__(self, rhs): return JHDate(self._date + rhs)
def __sub__(self, rhs): return JHDate(self._date - rhs)
def __format__(self, spec): return format(self._date, spec)
def __str__(self): return str(self._date)
def timetuple(self): return self._date.timetuple()
def toordinal(self): return self._date.toordinal()
def weekday(self): return self._date.weekday()
def isoweekday(self): return self._date.isoweekday()
def isocalendar(self): return self._date.isocalendar()
def isoformat(self): return self._date.isoformat()
def ctime(self): return self._date.ctime()
def strftime(self, fmt): return self._date.strftime(fmt)
def replace(self, year=None, month=None, day=None):
return JHDate(self._date.replace(
year if year is not None else self.year,
month if month is not None else self.month,
day if day is not None else self.day))
@staticmethod
def today():
return JHDate(datetime.date.today())
@staticmethod
def fromtimestamp(timestamp):
return JHDate(datetime.date.fromtimestamp(timestamp))
@staticmethod
def fromordinal(ordinal):
return JHDate(datetime.date.fromordinal(ordinal))
@staticmethod
def fromisoformat(iso):
return JHDate(datetime.date.fromisoformat(iso))
@staticmethod
def fromisocalendar(year, week, weekday):
return JHDate(datetime.date.fromisocalendar(year, week, weekday))
@staticmethod
def holidays(year, month=None):
return tuple(map(JHDate, JHoliday.holidays(year, month)))
if __name__ == '__main__':
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument('isodate', metavar='DATE', nargs='+', help='format: YYYY[-MM[-DD]]')
args = parser.parse_args()
holidays = []
for iso in args.isodate:
ymd = iso.split('-')
if len(ymd) == 1:
holidays += JHDate.holidays(int(ymd[0]))
elif len(ymd) == 2:
holidays += JHDate.holidays(int(ymd[0]), int(ymd[1]))
elif len(ymd) == 3:
holidays += [JHDate.fromisoformat(iso)]
else:
raise TypeError(iso)
WEEKDAY = ('月火水木金土日')
for date in sorted(holidays):
holiday = date.holiday
w = WEEKDAY[date.weekday()]
s = f'{date.year:4}年{date.month:2}月{date.day:2}日({w})'
print(f'{s}: {holiday}')
実行例
$ python3 jholiday.py 2024
2024年 1月 1日(月): 元日
2024年 1月 8日(月): 成人の日
2024年 2月11日(日): 建国記念の日
2024年 2月23日(金): 天皇誕生日
2024年 3月20日(水): 春分の日
2024年 4月29日(月): 昭和の日
2024年 5月 3日(金): 憲法記念日
2024年 5月 4日(土): みどりの日
2024年 5月 5日(日): こどもの日
2024年 7月15日(月): 海の日
2024年 8月11日(日): 山の日
2024年 9月16日(月): 敬老の日
2024年 9月23日(月): 秋分の日
2024年10月14日(月): スポーツの日
2024年11月 3日(日): 文化の日
2024年11月23日(土): 勤労感謝の日
$ python3 jholiday.py 2024-04 2024-05
2024年 4月29日(月): 昭和の日
2024年 5月 3日(金): 憲法記念日
2024年 5月 4日(土): みどりの日
2024年 5月 5日(日): こどもの日