[Python]カレンダーの作り方
カレンダーは、ブログの更新状況から営業スケジュールの表示、予約受付フォームの表示など用途は多く、広く使われています。今回はPythonで、実務でも必要になることが多いカレンダーを作成してみます。
目次
Pythonでカレンダーを作る方法
Webプログラムでカレンダーを表示するには、どのような機能が必要となるか洗い出してみます。まずカレンダーは、年と月が特定されて初めて表示できるものです。そこで年と月をブラウザから指定できる構成にします。
またカレンダー上には、第一週や最終週に前月や翌月の日付も表示されるものがあります。今回は、この機能も実装してみます。
1.データ「年、月」を受け取る ・GETリクエストで受け取る 2.データ「年、月」を表示データ「日付リスト」に変換する ・表示データは、曜日ごとのカレンダー形式で表示する ・表示データには、前月の日付、翌月の日付も含める 3.表示データを表示する ・カレンダーは、日曜から表示する ・土曜、日曜、祝祭日で色を変えて表示する
Pythonで月の日付の配列を作成する
カレンダー表示で必要となるデータは、カレンダーに表示する「日付リスト」です。これは指定した月については、月の日数が分かれば、求めることが出来ます。また第一週に含まれる前月分の日付リストは、月初1日の曜日から算出できそうです。同様に最終週に含まれる翌月分の日付リストも、月末の曜日から算出できそうです。
処理するデータは「年、月」は、ブラウザから受け取る必要があります。デコード用のモジュール cgi_decodeを使うので、こちらからダウンロードして下さい。
#!/usr/bin/python3
from datetime import datetime, timedelta, timezone
import calendar
import cgi_decode
cgi_decode.Set()
# GET,POST,COOKIE,FILES が使えるように
# タイムゾーンの生成
JST = timezone(timedelta(hours=+9), 'JST')
# 現在日時
date = datetime.now(JST)
# 年の指定 初期値を当年に
y = int(GET.get('year',date.strftime('%Y')))
# 月の指定 初期値を当月に
m = int(GET.get('month',date.strftime('%m')))
# 指定年月の1日の曜日と月の日数
w1, t1 = calendar.monthrange(y,m)
# 指定年月の前月の日数
(_y,_m) = (y-1,12) if m < 2 else (y,m-1)
_, t0 = calendar.monthrange(_y,_m)
# 指定年月の月末曜日
w2 = calendar.weekday(y,m,t1)
Pythonでは、標準ライブラリcalenderのmonthrange(年,月)で該当する月の1日の曜日と月の日数を取得することが出来ます。またweekday(年,月,日)で該当する日の曜日を取得できます。
曜日は0-6の数値になっていて、0が月曜で6が日曜になります。週は7日あり、月初1日の曜日が土曜(5)の時、第一週の前月分の日付は、日曜スタートのカレンダーでは6日あります。日曜(6)の場合は、0日になります。日曜が-1であれば計算しやすいので、-1にします。
また前月の月末日が31日だった場合、6日分のリストは、26,27,28,29,30,31になりますが、開始日を求める算出式としては 31-土曜(5)=26 となります。
# 日曜の場合-1に
if w1==6:
w1 = -1
if w2==6:
w2 = -1
# 月の第一週に含まれる前月の開始日を算出
st = t0-w1;
# 前月の第一週分の日付を生成
days = [] if t0<st else list(range(st,t0+1))
# 指定年月の日付を生成
d1 = list(range(1,t1+1))
# 月の最終週に含まれる翌月の最終日を算出
end = 7 -(w2+2)
# 翌月の最終週分の日付を生成
d2 = list(range(1,end+1)) if 0<end else []
# 生成した日付を一つの配列に統合
days = days + d1 + d2
Pythonでは、range(開始,終了)で指定した範囲の値の配列を作成できます。最終週に含まれる翌月の日付は、月末の曜日が土曜(5)の時、0日で、日曜(6→-1)の時、6日あります。よって、7 - (曜日+2) で求めることが出来ます。
# 配列を分割して2次元配列にする関数
def chunk(li,n):
return [li[x:x+n] for x in range(0,len(li),n)]
days = chunk(days,7)
日付リストはカレンダー表示の場合、7日毎に分けてあれば表示する際に処理しやすいです。Pythonでは、配列を分割する関数は標準ではないため、作成しています。
Pythonで祝祭日を設定する
祝祭日を算出するアルゴリズムを構築する方法もありますが、今回は休業日などに対応するため、配列に設定するかたちで実装します。
# 休日を判定する関数
def is_holiday(date):
# 2019年分
tu = (
# 祝祭日
'20190101',
'20190114',
'20190211',
'20190321',
'20190429',
'20190503',
'20190504',
'20190505',
'20190506',
'20190715',
'20190811',
'20190812',
'20190916',
'20190923',
'20191014',
'20191103',
'20191104',
'20191123',
'20191223',
# 休業日
'20190102',
'20191230',
'20191231',
)
return date in tu
Pythonでjsonデータとして返す
javascriptで動的に表示を切り替えたい場合は、jsonデータで渡すことになります。Pythonでは、標準ライブラリjsonのdumps()で配列や連想配列をjson形式に変換することが出来ます。
# JSONデータに変換して表示
import json
print("Content-Type: application/json; charset=utf-8\n")
print(json.dumps(days))
今回は、ブラウザで年月の指定も行いたいため、jsonデータをtextarea内に出力する構成で作成します。土曜は青色、日曜は赤色の文字色にし、前月、翌月の日付は灰色で表示する予定としてcssも作成しています。
# htmlデータで出力
if GET.get('type')=='html':
# jsonデータで出力
else:
html = '<textarea>'+ json.dumps(days) +'</textarea>'
r1 = ' checked'
r2 = ''
ht ='''
<title>[Python]カレンダーを表示</title>
<form>
<input type="number" name="year" min="1" max="3000" value="{}">年
<input type="number" name="month" min="1" max="12" value="{}">月
<label>
<input type="radio" name="type" value="json"{}>JSONデータ
</label>
<label>
<input type="radio" name="type" value="html"{}>HTMLデータ
</label>
<input type="submit" value="送信">
</form>
'''.format(y,m,r1,r2)
print(ht)
print(html)
ht ='''
<style>
form,textarea,table {
display: block;
margin: 30px auto;
width: 70%;
}
input {
margin-right: 5px;
padding: 5px;
}
th,td {
text-align: center;
}
.gray {
color: #aaa;
}
.red {
color: #f00;
}
.blue {
color: #00f;
}
</style>
'''
print(ht)
PythonでHTMLデータとして返す
最後にHTMLの状態で表示できるようにしてみます。日付リストは、週ごとの配列になっているので、第一週の時に出現した1日を基準として、前月分の日付を判定できます。また最終週に含まれる翌月の日付も1日を基準にして判定可能です。
日付リストの曜日は、週ごとの配列の1番目が日曜で、配列の添字0、最後が土曜で配列の添字は6です。下記のコードでは、i が週ごとの配列の添字、j が日付の配列の添字になっています。
# htmlデータで出力
if GET.get('type')=='html':
html = '''<table>
<tr>
<th class="red">日</th>
<th>月</th>
<th>火</th>
<th>水</th>
<th>木</th>
<th>金</th>
<th class="blue">土</th>
</tr>'''
# 文字色の初期値を灰色に
cls = 'gray'
for i,week in enumerate(days):
html += '<tr>'
for j,v in enumerate(week):
# 第一週の時
if i==0:
# 日付が1日
if v==1:
cls = ''
else:
# 第二週以降で日付が1日
if v==1:
cls = 'gray'
# 初期色以外の時
if cls != 'gray':
if j==0:
cls = 'red'
elif j==6:
cls = 'blue'
else:
cls = ''
html += '<td{}>{}</td>'.format((''if cls=='' else ' class="'+cls+'"'),v)
html += '</tr>'
html += '</table>'
r1 = ''
r2 = ' checked'
さらに作成した休日判定の関数を利用すれば、休業日だけ色を変えることも出来ます。注意点として、休日は年月日の8文字で設定したので、利用する場合は、format()などで日付を整形してから引数として渡す必要があります。