[Python]カレンダー表示アプリを作ってみた

Python

PythonでカレンダーをGUI表示させるデスクトップアプリを作成してみました。
その制作記録です。

GUIは外部パッケージのPySimpleGUI、その他に、標準ライブラリのcalendardatetimeを使用しました。

PySimpleGUIの使い方↓

完成したコードは70行ほどで、「まとめ」にあります。

使用した環境
Windows10
Python 3.10.0
PySimpleGUI 4.56.0

スポンサーリンク
スポンサーリンク

制作の概要

そもそもPySimpleGUIにはcalendarボタンが備わっていますが、それはカレンダーから選択された日付を受け取るものでしょう。

カレンダーボタンの使用

「Calendar」ボタン
カレンダー表示
「Calendar」ボタンの上に選択した日付が表示されている。

上掲のカレンダーは小さくて、カレンダーを表示させる専用のものではありません。

なので、練習もかねて自分で作ってみることにしました。

おおまかな手順としては、レイアウトの作成をし、メインループ部分を作っていきます。

実際に出来上がったものは、次のような画像です。

レイアウトの作成

各モジュールのインポートと必要な変数を定義します。

import calendar
import datetime

import PySimpleGUI as sg


sg.theme('LightBlue6')
today = datetime.date.today()
cal_date = today

cal_date」という変数に、カレンダー表示の基準となる月日のデータを保存します。

次に、レイアウトを作成する機能を「create_layout」関数にまとめました。

def create_layout(cal_date):
    weekday = ['SUN','MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']  
    cal = calendar.Calendar(firstweekday=6)
    days = cal.monthdatescalendar(cal_date.year, cal_date.month)
    layout = [[sg.Text(cal_date.year, font=(None, 13, 'bold'))],
              [sg.Push(), sg.Button('<<'), sg.Button('<'), sg.Text(cal_date.month, font=(None, 30)), sg.Button('>'), sg.Button('>>'), sg.Push()]]
    inner = []
    
    for week in weekday:
        inner.append(sg.Text(week, size=(4,1), text_color='white', background_color='green', justification='center'))
    layout.append(inner.copy())

    def date_judgement(i, day):
        if day == today:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='white', background_color='gray')
        elif i == 0 and day.month == cal_date.month:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='red')
        elif i == 6 and day.month == cal_date.month:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='blue')
        elif day.month == cal_date.month:
            return sg.Text(day.day, size=(4,1), justification='right')
        elif i == 0:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='#ff9999')
        elif i == 6:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='#9999ff')
        else:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='#cccccc')

    for row in days:
        inner = []
        for i, day in enumerate(row):
            sg_text = date_judgement(i, day)
            inner.append(sg_text)
        layout.append(inner.copy())
    return layout
    cal = calendar.Calendar(firstweekday=6)
    days = cal.monthdatescalendar(cal_date.year, cal_date.month)

カレンダークラスのオブジェクトを作成し、カレンダーの週初めの曜日を日曜日に指定しています。
days」に入るのはdetatimedate型の週のリストです。

最初の「layout」に代入しているのは次の部分です。

カレンダーの年、月の部分

続いてのfor文では、曜日の部分を「inner」に追加していき、最終的に「layout」の末尾に「inner」を追加するものです。
アプリの次の部分に当たります。

カレンダーの曜日部

最後のfor文で追加しているのが、日付に当たります。

row」に入るのは日曜日スタートの一週間分の日付なので、enumerate(row)で代入される「i」は曜日番号、「day」はdatetime.date型になります。

# 2022年4月分のdaysの初週のデータ
[datetime.date(2022, 3, 27), datetime.date(2022, 3, 28), datetime.date(2022, 3, 29), datetime.date(2022, 3, 30), datetime.date(2022, 3, 31), datetime.date(2022, 4, 1), datetime.date(2022, 4, 2)]

その際、「date_judgement」関数で、日付の判定を行いました。

引数の曜日番号と日付を使い、その日が今日なのか、カレンダーに表示させる当月の日曜かそれとも土曜日か、当月の平日か、当月ではない日曜・土曜・平日かを判定し、表示させる色を変えて、一行ごとに「inner」に追加するようにしています。

これも「layout」の末尾に追加し、最終的には「layout」を返します。

メインループ

def main():
    cal_date = today
    layout = create_layout(cal_date)
    window = sg.Window('Simple Calendar', layout)

    while True:
        event, _ = window.read()
        if event == sg.WIN_CLOSED:
            break
        elif event == '<':
            cal_date = datetime.date(cal_date.year, cal_date.month, 1) - datetime.timedelta(days=1)
        elif event == '>':
            cal_date = datetime.date(cal_date.year, cal_date.month, 28) + datetime.timedelta(days=4)
        elif event == '<<':
            cal_date = datetime.date(cal_date.year - 1, cal_date.month, 1)
        elif event == '>>':
            cal_date = datetime.date(cal_date.year + 1, cal_date.month, 1)
        window.close()
        window = sg.Window('Simple Calendar', create_layout(cal_date))
    window.close()

if __name__ == '__main__':
    main()

冒頭で定義していた「cal_date」をmain関数内部に移動しました。

メインループの方では、ボタンによる処理を変えています。

'<'ボタンの場合、基準となる「cal_date」の年、月を使い、日付を1日にし、timedeltaで1日分引くことで、「cal_date」を前月のものに変更します。

'>'ボタンが押されると、「cal_date」のデータを使い、datetime.date型の日付を28日にし、timedelataで4日分足して、基準を翌月のものに変更します。

'<<''>>'ボタンはcal_dateの年から1を足すか、引くか、なので問題ないでしょう。

上記のボタンが押されると、windowが閉じ、更新された「cal_date」を使用して新しいwindowを作ります。

まとめ

これまでのコードをひとつにまとめます。

import calendar
import datetime

import PySimpleGUI as sg


sg.theme('LightBlue6')
today = datetime.date.today()

def create_layout(cal_date):
    weekday = ['SUN','MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']  
    cal = calendar.Calendar(firstweekday=6)
    days = cal.monthdatescalendar(cal_date.year, cal_date.month)
    layout = [[sg.Text(cal_date.year, font=(None, 13, 'bold'))],
              [sg.Push(), sg.Button('<<'), sg.Button('<'), sg.Text(cal_date.month, font=(None, 30)), sg.Button('>'), sg.Button('>>'), sg.Push()]]
    inner = []
    
    for week in weekday:
        inner.append(sg.Text(week, size=(4,1), text_color='white', background_color='green', justification='center'))
    layout.append(inner.copy())

    def date_judgement(i, day):
        if day == today:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='white', background_color='gray')
        elif i == 0 and day.month == cal_date.month:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='red')
        elif i == 6 and day.month == cal_date.month:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='blue')
        elif day.month == cal_date.month:
            return sg.Text(day.day, size=(4,1), justification='right')
        elif i == 0:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='#ff9999')
        elif i == 6:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='#9999ff')
        else:
            return sg.Text(day.day, size=(4,1), justification='right', text_color='#cccccc')

    for row in days:
        inner = []
        for i, day in enumerate(row):
            sg_text = date_judgement(i, day)
            inner.append(sg_text)
        layout.append(inner.copy())
    return layout

def main():
    cal_date = today
    layout = create_layout(cal_date)
    window = sg.Window('Simple Calendar', layout)

    while True:
        event, _ = window.read()
        if event == sg.WIN_CLOSED:
            break
        elif event == '<':
            cal_date = datetime.date(cal_date.year, cal_date.month, 1) - datetime.timedelta(days=1)
        elif event == '>':
            cal_date = datetime.date(cal_date.year, cal_date.month, 28) + datetime.timedelta(days=4)
        elif event == '<<':
            cal_date = datetime.date(cal_date.year - 1, cal_date.month, 1)
        elif event == '>>':
            cal_date = datetime.date(cal_date.year + 1, cal_date.month, 1)
        window.close()
        window = sg.Window('Simple Calendar', create_layout(cal_date))
    window.close()

if __name__ == '__main__':
    main()

あまり利用していませんが、GitHubでも公開しました。

このページが少しでもお役に立てたのなら幸いです。

タイトルとURLをコピーしました