万年カレンダーを作成するプログラムです。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」に続きます↓