万年カレンダーを作成するプログラムです。Word・Excelへの出力や年表示・月表示・日本の祝日に対応しています。
PythonとAnacondaをインストールした後、JupyterNoteBookに以下のコマンドを入力し、実行してください(Wordファイルの操作で必要なモジュールのインストール)。
pip install python-docx
次に、JupyterNoteBookに以下の5つのファイルを作成し、以下に掲載されている各ソースコードを貼り付けてください。
・main
・CalenderArray
・HolidayListBoolean
・ExcelCalender
・WordCalender
その後、メインプログラム(main)を実行してください。
実行後、以下のダイアログが起動しますので、年・月・表示形式・保存フォルダ・ファイル名を指定し、ボタンをクリックすると指定された年月のカレンダーが作成されます。
(※起動時に表示されるファイル名は初期値で変更可能です)
(※年表示かつ祝日表示の場合は作成に時間がかかります)
作成が完了すると、以下のダイアログが表示されます。
作成されるカレンダーは以下の通りです(年月は一例です)。
[ソースコード]
[main]
# IPython Notebookモジュールのインポート
import importnb
# GUIアプリケーションモジュールのインポート
from tkinter import messagebox
from tkinter import filedialog
import tkinter as tk
import tkinter.ttk as ttk
# 別ファイルをインポート(jupyterモジュールインポート用)
with __import__('importnb').Notebook():
import ExcelCalender
import WordCalender
# カレンダー描画
def drawcalender():
# 入力チェック・フォームから値取得
christ = christoption.get()
if yearvar.get() == '':
messagebox.showerror('エラー', '年が入力されていません')
elif not yearvar.get().isdecimal(): # 10進数の正の数字かどうか判定
messagebox.showerror('エラー', '年は正の整数で入力してください')
else:
year = int(yearvar.get())
if year == 0:
messagebox.showerror('エラー', '西暦0年は存在しません')
else:
if christ == '紀元前':
year *= -1
month = int(monthvar.get())
form = displayform.get()
holiday = holidayboolean.get()
folder = folderlabel['text']
file = filename.get()
software = displaysoftware.get()
# 紀元前は祝日未定義とする
if year < 0:
holiday = False
# 入力チェック・カレンダー表示
if folder == '':
messagebox.showerror('エラー', 'フォルダ名が設定されていません')
elif file == '':
messagebox.showerror('エラー', 'ファイル名が設定されていません')
else:
root.destroy()
if software == 'word':
WordCalender.drawcalender(folder + '/' + file + '.docx', year, month, form, holiday)
elif software == 'excel':
ExcelCalender.drawcalender(folder + '/' + file + '.xlsx', year, month, form, holiday)
messagebox.showinfo('作業完了', 'カレンダーを作成しました')
# 月選択コンボボックスの状態を表示形態により切り替える
def combostate():
if displayform.get() == 'year':
monthvar['state'] = 'disable'
elif displayform.get() == 'month':
monthvar['state'] = 'readonly'
# ファイル格納フォルダを選択するダイアログを表示し、選択したフォルダのフルパスを設定する
def folderchoice():
folderlabel['text'] = filedialog.askdirectory()
# 入力フォーム
root = tk.Tk()
root.title('万年カレンダー')
root.geometry('400x150')
# チェックボタンに表示する文字列
christarray = ['紀元前', '紀元後']
montharray = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
# 祝日表示の可否の変数の戻り値を格納する辞書
holidayboolean = tk.BooleanVar()
# 表示形態を指定(ラジオボタンの状態)
displayform = tk.StringVar()
displaysoftware = tk.StringVar()
christoption = ttk.Combobox(root, textvariable = tk.StringVar(), values = christarray, width = 10, state ='readonly')
christoption.set('紀元後')
christoption.grid(row = 0, column = 0)
yearvar = tk.Entry(root, width = 10)
yearvar.grid(row = 0, column = 1)
yearlabel = tk.Label(root, text = ' 年 ')
yearlabel.grid(row = 0, column = 2)
monthvar = ttk.Combobox(root, textvariable = tk.IntVar(), values = montharray, width = 5, state ='readonly')
monthvar.grid(row = 0, column = 3)
monthvar.set('1')
monthlabel = tk.Label(root, text = ' 月 のカレンダー ')
monthlabel.grid(row = 0, column = 4)
yeardisplay = tk.Radiobutton(root, variable = displayform, text = ' 年表示 ', value = 'year', command = combostate)
yeardisplay.grid(row = 1, column = 0)
monthdisplay = tk.Radiobutton(root, variable = displayform, text = ' 月表示 ', value = 'month', command = combostate)
monthdisplay.grid(row = 1, column = 1, columnspan = 2)
displayform.set('month')
holidaydisplay = tk.Checkbutton(root, variable = holidayboolean, text = ' 祝日を表示する ')
holidaydisplay.grid(row = 1, column = 3, columnspan = 2)
folderbutton = tk.Button(root, text = 'ファイル保存フォルダ選択', command = folderchoice)
folderbutton.grid(row = 2, column = 0)
filetitle = tk.Label(root, text = 'ファイル名[拡張子除く]:')
filetitle.grid(row = 2, column = 1, columnspan = 2)
filename = tk.Entry(root, text = '', width = 20)
filename.grid(row = 2, column = 3, columnspan = 2)
filename.insert(0, 'calender')
foldertitle = tk.Label(root, text = 'ファイル保存フォルダ:')
foldertitle.grid(row = 3, column = 0)
folderlabel = tk.Label(root, text = '')
folderlabel.grid(row = 3, column = 1, columnspan = 4)
worddisplay = tk.Radiobutton(root, variable = displaysoftware, text = ' wordで表示 ', value = 'word')
worddisplay.grid(row = 4, column = 0)
exceldisplay = tk.Radiobutton(root, variable = displaysoftware, text = ' excelで表示 ', value = 'excel')
exceldisplay.grid(row = 4, column = 1, columnspan = 2)
displaysoftware.set('excel')
button = tk.Button(root, text = '表示', command = drawcalender)
button.grid(row = 5, column = 0, columnspan = 5)
root.mainloop()
[CalenderArray]
# カレンダーモジュールのインポート
import calendar
# 該当年月のカレンダーの2次元配列(1582年10月改暦)
def calenderarray(year, month):
# 週の始まりの曜日を日曜日に指定したカレンダーオブジェクトを生成
c = calendar.Calendar(firstweekday=6)
# 紀元前(28年周期)
if year < 0:
y = (year % 28) + 2005
return c.monthdayscalendar(y, month)
# 西暦0年は存在しない
elif year == 0:
return [[0]*7]*4
# 紀元後1581年まで(28年周期)
elif year <= 1581:
y = (year % 28) + 2004
return c.monthdayscalendar(y, month)
# 紀元後1582年
elif year == 1582:
if month <= 9:
return c.monthdayscalendar(2018, month)
elif month == 10:
return [[0, 1, 2, 3, 4, 15, 16],
[17, 18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29, 30],
[31, 0, 0, 0, 0, 0, 0]]
else:
return c.monthdayscalendar(year, month)
else:
return c.monthdayscalendar(year, month)
[HolidayListBoolean]
import calendar
import math
from datetime import datetime, date, timedelta
# 祝日オブジェクトの定義
class Holiday:
# 初期設定
def __init__(self, month, day, name, startyear, endyear):
self.month = month
self.day = day
self.name = name
self.startyear = startyear
self.endyear = endyear
# その年の日本の祝日一覧の配列
def JapanseHoliday(year):
# 正の無限大
f_inf = float('inf')
# 日本の祝日の配列を定義([月,日,祝日名,開始年,終了年])
jh = [Holiday(1, 1, '元日', 1949, f_inf),
Holiday(1, NthDay(year, 1, 2, '月'), '成人の日', 2000, f_inf),
Holiday(1, 15, '成人の日', 1949, 1999),
Holiday(2, 11, '建国記念の日', 1967, f_inf),
Holiday(2, 23, '天皇誕生日', 2020, f_inf),
Holiday(2, 24, '昭和天皇の大喪の礼', 1989, 1989),
Holiday(3, VernalEquinoxDay(year), '春分の日', 1949, f_inf),
Holiday(4, 10, '明仁親王の結婚の儀', 1959, 1959),
Holiday(4, 29, '天皇誕生日', 1949, 1988),
Holiday(4, 29, 'みどりの日', 1989, 2006),
Holiday(4, 29, '昭和の日', 2007, f_inf),
Holiday(5, 1, '新天皇即位の日', 2019, 2019),
Holiday(5, 3, '憲法記念日', 1949, f_inf),
Holiday(5, 4, 'みどりの日', 2007, f_inf),
Holiday(5, 5, 'こどもの日', 1949, f_inf),
Holiday(6, 9, '徳仁親王の結婚の儀', 1993, 1993),
Holiday(7, 20, '海の日', 1996, 2002),
Holiday(7, NthDay(year, 7, 3, '月'), '海の日', 2003, 2019),
Holiday(7, NthDay(year, 7, 3, '月'), '海の日', 2022, f_inf),
Holiday(7, 22, '海の日', 2021, 2021),
Holiday(7, 23, '海の日', 2020, 2020),
Holiday(7, 23, 'スポーツの日', 2021, 2021),
Holiday(7, 24, 'スポーツの日', 2020, 2020),
Holiday(8, 8, '山の日', 2021, 2021),
Holiday(8, 10, '山の日', 2020, 2020),
Holiday(8, 11, '山の日', 2016, 2019),
Holiday(8, 11, '山の日', 2022, f_inf),
Holiday(9, 15, '敬老の日', 1966, 2002),
Holiday(9, NthDay(year, 9, 3, '月'), '敬老の日', 2003, f_inf),
Holiday(9, AutumnEquinoxDay(year), '秋分の日', 1948, f_inf),
Holiday(10, 10, '体育の日', 1966, 1999),
Holiday(10, NthDay(year, 10, 2, '月'), '体育の日', 2000, 2019),
Holiday(10, NthDay(year, 10, 2, '月'), 'スポーツの日', 2022, f_inf),
Holiday(10, 22, '即位礼正殿の儀', 2019, 2019),
Holiday(11, 3, '文化の日', 1948, f_inf),
Holiday(11, 12, '即位礼正殿の儀', 1990, 1990),
Holiday(11, 23, '勤労感謝の日', 1948, f_inf),
Holiday(12, 23, '天皇誕生日', 1989, 2018),
]
return jh
# その月の第x回目のy曜日の日付
def NthDay(year, month, nth, dayofweek):
# 曜日を表す文字列の配列
japday = ['月', '火', '水', '木', '金', '土', '日']
engday = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
if nth < 1:
return None
# 曜日番号の抽出
for i in range(len(japday)):
if (japday[i] in dayofweek) or (engday[i] in dayofweek.lower()):
dow = i
break
# その月の1日の曜日番号(月曜0~日曜6)と日数
onedow, n = calendar.monthrange(year, month)
day = 7 * (nth - 1) + (dow - onedow) % 7 + 1
return day if day <= n else None
# math.floor:x以下の最大の整数(負の無限大への丸め)
# math.ceil:x以上の最小の整数(正の無限大への丸め)
# int:符号はそのままで絶対値を切り捨てる(0への丸め)
# 春分の日付(3月)[http://hp.vector.co.jp/authors/VA006522/zatugaku/syunbun.txtより引用し、一部修正]
def VernalEquinoxDay(year):
if 1583 <= year and year <= 1899: # 1850年以前は引用した計算式より推測
return math.floor(20.8277 + 0.242194 * (year - 1980) - math.floor((year - 1980) / 4) +
math.floor((year - 2000) / 100) - math.floor((year - 2000) / 400))
elif 1900 <= year and year <= 1979:
return math.floor(20.8357 + 0.242194 * (year - 1980) - math.floor((year - 1980) / 4))
elif 1980 <= year and year <= 2099:
return math.floor(20.8431 + 0.242194 * (year - 1980) - math.floor((year - 1980) / 4))
elif 2100 <= year: # 2151年以降は引用した計算式より推測
return math.floor(20.851 + 0.242194 * (year - 1980) - math.floor((year - 1980) / 4) +
math.floor((year - 2000) / 100) - math.floor((year - 2000) / 400))
# 秋分の日付(9月)[http://hp.vector.co.jp/authors/VA006522/zatugaku/syunbun.txtより引用し、一部修正]
def AutumnEquinoxDay(year):
if 1583 <= year and year <= 1899: # 1850年以前は引用した計算式より推測
return math.floor(23.2588 + 0.242194 * (year - 1980) - math.floor((year - 1980) / 4) +
math.floor((year - 2000) / 100) - math.floor((year - 2000) / 400))
elif 1900 <= year and year <= 1979:
return math.floor(23.2588 + 0.242194 * (year - 1980) - math.floor((year - 1980) / 4))
elif 1980 <= year and year <= 2099:
return math.floor(23.2488 + 0.242194 * (year - 1980) - math.floor((year - 1980) / 4))
elif 2100 <= year: # 2151年以降は引用した計算式より推測
return math.floor(23.2488 + 0.242194 * (year - 1980) - math.floor((year - 1980) / 4) +
math.floor((year - 2000) / 100) - math.floor((year - 2000) / 400))
# その年の祝日であるかどうかの判定・祝日一覧
def HolidayBooleanList(year):
# 祝日配列
hl = JapanseHoliday(year)
# 祝日であるかどうかの判定(日ごと)の配列
hb = [0] * 12
# その年の祝日一覧
th = []
for i in range(0, 12):
# その月の1日の曜日番号(月曜0~日曜6)と日数
onedow, n = calendar.monthrange(year, i+1)
hb[i] = [False] * n
# 祝日の項目ごとに該当する日付を真にする
for k in hl:
if (k.startyear <= year and year <= k.endyear):
hb[k.month-1][k.day-1] = True
# 祝日一覧の配列に格納(配列そのものを追加)
th.append([k.month, k.day, k.name])
return [hb, th]
# その日が祝日であるかどうかの判定
def HolidayBoolean(year, month, day):
# その年の祝日であるかどうかの判定一覧
hbl = HolidayBooleanList(year)
hb = hbl[0]
return hb[month-1][day-1]
# その年の祝日一覧
def HolidayList(year):
hbl = HolidayBooleanList(year)
return hbl[1]
# その年の祝日であるかどうかの判定・祝日一覧(振替休日・国民の休日を含む)
def SubHoliday(year):
# 振替休日・国民の休日であるかどうかの判定(日ごと)の配列
sb = [0] * 12
# その年の祝日であるかどうかの判定一覧
hb = HolidayList(year)
# その年の祝日一覧
hl = HolidayList(year)
# 振替休日・国民の休日であるかどうかの判定(祝日でないかどうかが前提)
for i in range(0, 12):
# その月の1日の曜日番号(月曜0~日曜6)と日数
onedow, n = calendar.monthrange(year, i+1)
sb[i] = [False] * n
for j in range(0, n):
if not HolidayBoolean(year, (i+1), (j+1)):
# 振替休日の判定(1973年4月12日~2006年12月31日):祝日が日曜の場合、その翌日
if (year == 1973 and (((i+1) == 4 and (j+1) >= 12) or (i+1) >= 5)) or (1974 <= year and year <= 2006):
pd = date(year, (i+1), (j+1)) - timedelta(days = 1)
if HolidayBoolean(pd.year, pd.month, pd.day) and pd.weekday() == 6:
sb[i][j] = True
# その年の祝日一覧に追加(配列そのものを追加)
hl.append([(i+1), (j+1), '振替休日'])
# 振替休日の判定(2007年1月1日~):祝日が日曜の場合、その翌平日
elif (2007 <= year):
pd = date(year, (i+1), (j+1)) - timedelta(days = 1)
while HolidayBoolean(pd.year, pd.month, pd.day):
if pd.weekday() == 6:
sb[i][j] = True
# その年の祝日一覧に追加(配列そのものを追加)
hl.append([(i+1), (j+1), '振替休日'])
break
pd = pd - timedelta(days = 1)
# 国民の休日の判定(1985年12月27日~):前日・翌日がともに祝日でその日が祝日かつ振替休日でない場合
if (year == 1985 and (i+1) == 12 and (j+1) >= 27) or 1986 <= year:
pd = date(year, (i+1), (j+1)) - timedelta(days = 1)
nd = date(year, (i+1), (j+1)) + timedelta(days = 1)
if (HolidayBoolean(pd.year, pd.month, pd.day) and HolidayBoolean(nd.year, nd.month, nd.day)) and not sb[i][j]:
sb[i][j] = True
# その年の祝日一覧に追加(配列そのものを追加)
hl.append([(i+1), (j+1), '国民の休日'])
return [hb, sb, hl]
# その日が振替休日・国民の休日であるかどうかの判定
def SubHolidayBoolean(year, month, day):
# その年の祝日であるかどうかの判定一覧
sbl = SubHoliday(year)
sb = sbl[1]
return sb[month-1][day-1]
# その年の祝日一覧(振替休日・国民の休日を含む)
def SubHolidayList(year):
sbl = SubHoliday(year)
return sbl[2]
# その日の祝日名
def SubHolidayName(year, month, day):
# その年の祝日一覧(振替休日・国民の休日を含む)
shl = SubHolidayList(year)
# その年の祝日であるかどうかの判定一覧
if HolidayBoolean(year, month, day) or SubHolidayBoolean(year, month, day):
for i in shl:
if (i[0] == month) and (i[1] == day):
return i[2]
break
else:
return ''
[ExcelCalender]
# IPython Notebookモジュールのインポート
import importnb
# 別ファイルをインポート(jupyterモジュールインポート用)
with __import__('importnb').Notebook():
import CalenderArray
import HolidayListBoolean
# openpyxl(Excelライブラリー[excelファイルのみ取扱可能])・書式ライブラリー・罫線ライブラリー読込
import openpyxl as px
from openpyxl.styles import Alignment
from openpyxl.styles import PatternFill
from openpyxl.styles.fonts import Font
# ファイルライブラリー読込
import os
# カレンダー描画
def drawcalender(filename, year, month, form, holiday):
# 新規Excelブックを作成する
wb = px.Workbook()
# シート指定
ws = wb.worksheets[0]
# カレンダー表示
if form == 'year':
yearcalender(ws, year, 1, 1, holiday)
elif form == 'month':
monthcalender(ws, year, month, 1, 1, holiday)
# ファイルを保存
wb.save(filename)
# ファイルを閉じる
wb.close()
# 月間カレンダー表示
def monthcalender(ws, year, month, rownum, colnum, holiday):
# カレンダー取得
c = CalenderArray.calenderarray(year, month)
# 日本の祝日一覧(振替休日・国民の休日を含む)
jh = HolidayListBoolean.SubHolidayList(year)
# その月の祝日一覧を配列に格納(配列そのものを追加)
hl = []
for k in jh:
if k[0] == month:
hl.append([k[1], k[2]])
# 配列の並べ替え(日付の昇順)
hl.sort()
if year < 0:
ws.cell(row = rownum, column = colnum).value = '紀元前' + str(-year) + '年' + str(month) + '月'
else:
ws.cell(row = rownum, column = colnum).value = str(year) + '年' + str(month) + '月'
ws.cell(row = rownum, column = colnum).font = Font(bold=True, size=12)
for j in range(colnum, colnum+7):
ws.cell(row = rownum, column = j).alignment = Alignment(horizontal = 'centerContinuous')
for i in range(len(c)):
for j in range(len(c[i])):
if c[i][j] != 0:
ws.cell(row = rownum+i+1, column = colnum+j).value = str(c[i][j])
ws.cell(row = rownum+i+1, column = colnum+j).alignment = Alignment(horizontal = 'right')
ws.column_dimensions[ws.cell(row = rownum+i+1, column = colnum+j).column_letter].width = 4
# 曜日別にカラーリング
if j == 0:
ws.cell(row = rownum+i+1, column = colnum+j).font = Font(color='FF0000')
elif j == 6:
ws.cell(row = rownum+i+1, column = colnum+j).font = Font(color='0000FF')
else:
ws.cell(row = rownum+i+1, column = colnum+j).font = Font(color='000000')
# 祝日関連
if holiday:
if HolidayListBoolean.HolidayBoolean(year, month, c[i][j]):
ws.cell(row = rownum+i+1, column = colnum+j).fill = PatternFill(patternType='solid',
fgColor='FF0000', bgColor='FF0000')
ws.cell(row = rownum+i+1, column = colnum+j).font = Font(color='FFFFFF')
# 振替休日・国民の休日
elif HolidayListBoolean.SubHolidayBoolean(year, month, c[i][j]):
ws.cell(row = rownum+i+1, column = colnum+j).font = Font(color='FF0000')
# 祝日一覧の表示
for k in range(len(hl)):
ws.cell(row = rownum+len(c)+k+2, column = colnum).value = str(hl[k][0])
ws.cell(row = rownum+len(c)+k+2, column = colnum).alignment = Alignment(horizontal = 'right')
ws.cell(row = rownum+len(c)+k+2, column = colnum+1).value = str(hl[k][1])
if ('振替休日' in hl[k][1]) or ('国民の休日' in hl[k][1]):
for l in range(0, 7):
ws.cell(row = rownum+len(c)+k+2, column = colnum+l).font = Font(color='FF0000')
else:
for l in range(0, 7):
ws.cell(row = rownum+len(c)+k+2, column = colnum+l).fill = PatternFill(patternType='solid',
fgColor='FF0000', bgColor='FF0000')
ws.cell(row = rownum+len(c)+k+2, column = colnum+l).font = Font(color='FFFFFF')
# 年間カレンダー表示
def yearcalender(ws, year, rownum, colnum, holiday):
for i in range(0, 4):
# 値が入力されている最終行を取得(最終行は自動的に変化)
lastrow = ws.max_row
for j in range(0, 3):
monthcalender(ws, year, i*3+j+1, lastrow+2, j*8+1, holiday)
if year < 0:
ws.cell(row = 1, column = 1).value = '紀元前' + str(-year) + '年' + 'のカレンダー'
else:
ws.cell(row = 1, column = 1).value = str(year) + '年' + 'のカレンダー'
for j in range(1, 8*3):
ws.cell(row = 1, column = j).alignment = Alignment(horizontal = 'centerContinuous')
ws.cell(row = 1, column = j).font = Font(bold=True, size=16)
↓「万年カレンダー作成プログラムソースコード(Python)_2」に続きます↓