naritoブログ

【お知らせ】
新ブログができました。今後そちらで更新し、このサイトは更新されません(ウェブサイト自体は残しておきます)
このブログの内容に関してコメントしたい場合は、新ブログのフリースペースに書き込んでください

このブログの内容を新ブログに移行中です。このブログで見つからない記事は、新ブログにありま

Pythonで、月間カレンダーをつくる

約742日前 2017年7月3日21:59
プログラミング関連
Bootstrap4 Python calendarモジュール

概要


Pythonで、カレンダーを作ってみます。

シンプルなカレンダー


カレンダーを作成する場合、標準ライブラリの「calendar」モジュールが便利です。
もっとも単純な例ならば、例えば以下です。

"""カレンダーをコンソールに表示してみる."""
import calendar
my_calendar = calendar.TextCalendar()
my_calendar_string = my_calendar.formatmonth(2018, 10)
print(my_calendar_string)


これは以下のような出力になります。

October 2018
Mo Tu We Th Fr Sa Su
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31


TextCalendarにロケール情報を追加した、LocaleTextCalendarもあります。

my_calendar = calendar.LocaleTextCalendar()



10月 2018
月 火 水 木 金 土 日
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31


HTMLなカレンダー


ウェブアプリならば、HTMLとして出力されるHTMLCalendar、LocaleHTMLCalendarが便利です。

"""カレンダーのhtmlをコンソールに出力してみる."""
import calendar
my_calendar = calendar.LocaleHTMLCalendar()
my_calendar_string = my_calendar.formatmonth(2018, 10)
print(my_calendar_string)


tableとして出力がされます。(下記の出力は整形済みのもの)

<table border="0" cellpadding="0" cellspacing="0" class="month">
<tr>
<th colspan="7" class="month">10月 2018</th>
</tr>
<tr>
<th class="mon">月</th>
<th class="tue">火</th>
<th class="wed">水</th>
<th class="thu">木</th>
<th class="fri">金</th>
<th class="sat">土</th>
<th class="sun">日</th>
</tr>
<tr>
<td class="mon">1</td>
<td class="tue">2</td>
<td class="wed">3</td>
<td class="thu">4</td>
<td class="fri">5</td>
<td class="sat">6</td>
<td class="sun">7</td>
</tr>
<tr>
<td class="mon">8</td>
<td class="tue">9</td>
<td class="wed">10</td>
<td class="thu">11</td>
<td class="fri">12</td>
<td class="sat">13</td>
<td class="sun">14</td>
</tr>
<tr>
<td class="mon">15</td>
<td class="tue">16</td>
<td class="wed">17</td>
<td class="thu">18</td>
<td class="fri">19</td>
<td class="sat">20</td>
<td class="sun">21</td>
</tr>
<tr>
<td class="mon">22</td>
<td class="tue">23</td>
<td class="wed">24</td>
<td class="thu">25</td>
<td class="fri">26</td>
<td class="sat">27</td>
<td class="sun">28</td>
</tr>
<tr>
<td class="mon">29</td>
<td class="tue">30</td>
<td class="wed">31</td>
<td class="noday">&nbsp;</td>
<td class="noday">&nbsp;</td>
<td class="noday">&nbsp;</td>
<td class="noday">&nbsp;</td>
</tr>
</table>



htmlとして保存し、開いてみましょう。
標準ライブラリのwebbrowserモジュールを使うと、すぐにブラウザで確認できます。

"""月ごとのカレンダーのhtmlを作成し、ブラウザで開く."""
import calendar
import webbrowser

my_calendar = calendar.LocaleHTMLCalendar()
my_calendar_html = my_calendar.formatmonth(2018, 10)
with open('calendar.html', 'w') as file:
file.write(my_calendar_html)

webbrowser.open_new('calendar.html')


以下のようになります。


各カレンダーは柔軟にカスタマイズ可能です。継承し、必要なメソッドを上書きするだけです。
試しに上部のタイトル部分を書き換えましょう。formatmonthnameメソッドを上書きします。

"""月ごとのカスタムカレンダーのhtmlを作成し、ブラウザで開く."""
import calendar
import webbrowser


class MyCalendar(calendar.LocaleHTMLCalendar):
"""カスタムカレンダー."""

def formatmonthname(self, theyear, themonth, withyear=True):
"""tableタグの一番上、タイトル部分にあたるhtmlを作成する."""
return '<tr><th colspan="7" class="month">Hello World</th></tr>'


my_calendar = MyCalendar()
my_calendar_html = my_calendar.formatmonth(2018, 10)
with open('calendar.html', 'w') as file:
file.write(my_calendar_html)

webbrowser.open_new('calendar.html')


ちゃんと書き換わりました!


各日付部分をaタグにしてみましょう。

"""月ごとのカスタムカレンダーのhtmlを作成し、ブラウザで開く."""
import calendar
import webbrowser


class MyCalendar(calendar.LocaleHTMLCalendar):
"""カスタムカレンダー."""

def formatmonthname(self, theyear, themonth, withyear=True):
"""tableタグの一番上、タイトル部分にあたるhtmlを作成する."""
return '<tr><th colspan="7" class="month">Hello World</th></tr>'

def formatday(self, day, weekday):
"""tableタグの日付部分のhtmlを作成する<td>...</td>."""
if day == 0:
return '<td class="noday">&nbsp;</td>' # day outside month
else:
base_html = '<td class="{}"><a href="#">{}</a></td>'
return base_html.format(self.cssclasses[weekday], day)

my_calendar = MyCalendar()
my_calendar_html = my_calendar.formatmonth(2018, 10)
with open('calendar.html', 'w') as file:
file.write(my_calendar_html)

webbrowser.open_new('calendar.html')


ちゃんと日付部分がリンクになりました。


HTMLカレンダー+Bootstrap4


更に、Bootstrap4を使ってみましょう。
Bootstrapなので、tableタグのclassを変更しておきましょう。
この場合は、formatmonthメソッドを上書きします。

"""月ごとのカスタムカレンダーのhtmlを作成し、ブラウザで開く."""
import calendar
import webbrowser

BASE_HTML = """
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<!-- ここにカレンダーを挿入する -->
{}
</div>
<!-- jQuery first, then Tether, then Bootstrap JS. -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.2.0/js/tether.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
</body>
</html>
"""


class MyCalendar(calendar.LocaleHTMLCalendar):
"""カスタムカレンダー."""

def formatmonthname(self, theyear, themonth, withyear=True):
"""tableタグの一番上、タイトル部分にあたるhtmlを作成する."""
return '<tr><th colspan="7" class="month">Hello World</th></tr>'

def formatday(self, day, weekday):
"""tableタグの日付部分のhtmlを作成する<td>...</td>."""
if day == 0:
return '<td class="noday">&nbsp;</td>' # day outside month
else:
base_html = '<td class="{}"><a href="#">{}</a></td>'
return base_html.format(self.cssclasses[weekday], day)

def formatmonth(self, theyear=None, themonth=None, withyear=True):
"""月のカレンダーを作成する."""
if theyear is None:
theyear = self.date.year
if themonth is None:
themonth = self.date.month
v = []
a = v.append
# classにtableを足しただけ!
a('<table class="month table">')
a('\n')
a(self.formatmonthname(theyear, themonth, withyear=withyear))
a('\n')
a(self.formatweekheader())
a('\n')
for week in self.monthdays2calendar(theyear, themonth):
a(self.formatweek(week))
a('\n')
a('</table>')
a('\n')
return ''.join(v)


my_calendar = MyCalendar()
my_calendar_html = my_calendar.formatmonth(2018, 10)
result_html = BASE_HTML.format(my_calendar_html)
with open('calendar.html', 'w') as file:
file.write(result_html)

webbrowser.open_new('calendar.html')



結構見れるようになりました。


今回は問題ないのですが、各列の大きさがバラバラになるならば以下のようなcssを指定します。

.table {
table-layout: fixed;
}


もし縦、横に罫線がほしければ以下のようなCSSを指定すると良いです。
曜日部分の罫線が不要なら、thの部分を消します。

.table th, .table td {
border: 1px solid #eceeef;
}




各行の高さを指定するならば、例えば以下のように。

.table tr{
height: 100px;
}



Bootstrap4なしカレンダー


Bootstrap4を使いましたが、もしこれを使いたくない、という場合は例えば以下のようにしてみましょう。
Bootstrap4のcssをいくつか拝借しており、ある程度整った見た目なはずです。

"""月ごとのカスタムカレンダーのhtmlを作成し、ブラウザで開く."""
import calendar
import webbrowser

BASE_HTML = """
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<!-- style -->
{}
</head>
<body>
<!-- ここにカレンダーを挿入する -->
{}
</body>
</html>
"""

STYLE = """
<style>
.month-table {
width: 100%;
max-width: 100%;
margin-bottom: 1rem;
table-layout: fixed;
}

.month-table td, .month-table th {
border: 1px solid #eceeef;
padding: .75rem;
vertical-align: top;
}

.month-table tr{
height: 100px;
}
</style>
"""


class MyCalendar(calendar.LocaleHTMLCalendar):
"""カスタムカレンダー."""


def formatday(self, day, weekday):
"""tableタグの日付部分のhtmlを作成する<td>...</td>."""
if day == 0:
return '<td class="noday">&nbsp;</td>' # day outside month
else:
base_html = '<td class="{}"><a href="#">{}</a></td>'
return base_html.format(self.cssclasses[weekday], day)

def formatmonth(self, theyear=None, themonth=None, withyear=True):
"""月のカレンダーを作成する."""
if theyear is None:
theyear = self.date.year
if themonth is None:
themonth = self.date.month
v = []
a = v.append
# classにtableを足しただけ!
a('<table class="month-table">')
a('\n')
a(self.formatmonthname(theyear, themonth, withyear=withyear))
a('\n')
a(self.formatweekheader())
a('\n')
for week in self.monthdays2calendar(theyear, themonth):
a(self.formatweek(week))
a('\n')
a('</table>')
a('\n')
return ''.join(v)


my_calendar = MyCalendar()
my_calendar_html = my_calendar.formatmonth(2018, 10)
result_html = BASE_HTML.format(STYLE, my_calendar_html)
with open('calendar.html', 'w') as file:
file.write(result_html)

webbrowser.open_new('calendar.html')





3.6ではまだですが、現在の最新のソースコードを見ると、HTMLCalendarクラスの属性が増えています。
例えば、tableタグのclassの変更だけならば以下のようにcssclass_monthを上書きするだけで良くなりそうです。今後の拡張が待ち遠しいですね。

class MyCalendar(calendar.LocaleHTMLCalendar):
"""カスタムカレンダー."""

cssclass_month = "month"



年間カレンダー


formatmonth以外にも、1年分のカレンダーのtableタグを作るformatyearもあります。

"""年ごとのカスタムカレンダーのhtmlを作成し、ブラウザで開く."""
import calendar
import webbrowser

BASE_HTML = """
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<!-- ここにカレンダーを挿入する -->
{}
</div>
<!-- jQuery first, then Tether, then Bootstrap JS. -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.2.0/js/tether.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
</body>
</html>
"""


class MyCalendar(calendar.LocaleHTMLCalendar):
"""カスタムカレンダー."""

def formatyear(self, theyear, width=3):
"""一年分のカレンダーを作成する."""
v = []
a = v.append
width = max(width, 1)
# classにtableを足しただけ!
a('<table border="0" cellpadding="0" cellspacing="0" class="year table">')
a('\n')
a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
for i in range(calendar.January, calendar.January+12, width):
# months in this row
months = range(i, min(i+width, 13))
a('<tr>')
for m in months:
a('<td>')
a(self.formatmonth(theyear, m, withyear=False))
a('</td>')
a('</tr>')
a('</table>')
return ''.join(v)


my_calendar = MyCalendar()
my_calendar_html = my_calendar.formatyear(2018)
result_html = BASE_HTML.format(my_calendar_html)
with open('calendar.html', 'w') as file:
file.write(result_html)

webbrowser.open_new('calendar.html')



名無し 約487日前 2018年3月15日21:49 返信する
本記事に掲載されている以下のコードで、①,②までは問題ないのですが、
③を実行すると「Error: unsupported locale setting」が発生してしまいます。

---------------------------------------------------------------------
①import calendar
②my_calendar = calendar.LocaleHTMLCalendar()
③my_calendar_string = my_calendar.formatmonth(2018, 10)
---------------------------------------------------------------------
環境は、DJANG2.01、WIndows10, python 3.6.3です。

具体的なエラー内容は以下の通りですが、何か環境的に設定が不足してるかわかりますでしょうか?
いろいろと調べてみましたがこれといって合致する情報が見つからず困っております。

In [41]: my_calendar_html = my_calendar.formatmonth(2018, 10)
---------------------------------------------------------------------------
Error Traceback (most recent call last)
<ipython-input-41-7a6358981707> in <module>()
----> 1 my_calendar_html = my_calendar.formatmonth(2018, 10)

~\Miniconda3\envs\myenv\lib\calendar.py in formatmonth(self, theyear, themonth, withyear)
430 a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
431 a('\n')
--> 432 a(self.formatmonthname(theyear, themonth, withyear=withyear))
433 a('\n')
434 a(self.formatweekheader())

~\Miniconda3\envs\myenv\lib\calendar.py in formatmonthname(self, theyear, themonth, withyear)
549
550 def formatmonthname(self, theyear, themonth, withyear=True):
--> 551 with different_locale(self.locale):
552 s = month_name[themonth]
553 if withyear:

~\Miniconda3\envs\myenv\lib\calendar.py in __enter__(self)
493 def __enter__(self):
494 self.oldlocale = _locale.getlocale(_locale.LC_TIME)
--> 495 _locale.setlocale(_locale.LC_TIME, self.locale)
496
497 def __exit__(self, *args):

~\Miniconda3\envs\myenv\lib\locale.py in setlocale(category, locale)
596 # convert to string
597 locale = normalize(_build_localename(locale))
--> 598 return _setlocale(category, locale)
599
600 def resetlocale(category=LC_ALL):

Error: unsupported locale setting
なりと 約487日前 2018年3月15日23:43
ロケールの設定が何かおかしいようです。
ちょっとWindows環境での設定がよくわからないのですが、コードの先頭に
import locale
locale.setlocale(locale.LC_ALL, '')
と足してみてください。

それでもダメなら、コード中のLocaleHTMLCalendarをHTMLCalendarに変更してください。○年☓月や曜日が英語になりますが、動くはずです。
calendar.LocaleHTMLCalendar

calendar.HTMLCalendar
名無し 約486日前 2018年3月16日13:43 返信する
ご回答ありがとうございます!
回答内容の通り変更して試してみたいと思います。
ちなみにGithub上のモジュールをpipでインストールした場合も同じエラーが発生しますが、もし可能であれば、Github上のモジュールについても変更箇所がわかったりしますでしょうか?
なりと 約486日前 2018年3月16日14:09
Github上のソースコードも、calendar.LocaleHTMLCalendarを修正すれば動くと思いますので
後ほど修正しておきます。
名無し 約486日前 2018年3月16日14:18 返信する
たびたびすいません・・
その後こちらで以下のように変更してみたところ、pipで導入したGithub上モジュールでカレンダーが問題なく表示できるようになりました。

\site-packages\django_calendar\calendarlib.py

■2行目
from calendar import (
month_name, monthrange, LocaleHTMLCalendar, different_locale
)

→from calendar import (
month_name, monthrange, HTMLCalendar, different_locale
)

■49行目  
class SimpleCalendarBS4(LocaleHTMLCalendar):
→class SimpleCalendarBS4(HTMLCalendar):


■153行目
with different_locale(self.locale):

→with different_locale(locale=None):
名無し 約486日前 2018年3月16日14:35 返信する
カレンダーを使っていて思ったのですが、一度登録した内容を変更できる編集用View機能もあると便利だなと感じました。
もしUpdateされる機会がありましたら検討いただければとおもいます!
なりと 約486日前 2018年3月16日14:50
おっしゃるとおりで、そちらを変更すれば動作します。Githubのソースコードも修正しました。多分いずれ作り直すので、その際に編集用Viewもつけると思います。