前編
https://ameblo.jp/araya-benki/entry-12795889773.html
後編
https://ameblo.jp/araya-benki/entry-12796631197.html
特別編


前回までのあらすじ
100年カレンダーをPythonで作ってみることにしました。
本のDL特典を無視して、自分で作ろうとしているのは、
自分の寿命と、偉人(異人?)の寿命とを記録したカレンダーにしたかったから。
そして、それぞれ各自で自分用の100年カレンダーを作れるようにしたかったから。
というわけで、使っていないExcelは使わないまま、
Excel代理のLibreOfficeを使って印刷するものを作ってみます。
そして、エジソンは悪い人で確定です。

必要なもの
Pythonできるもの
Microsoft OfficeとかについているExceまたはLibreOfficeのCalc

Pythonのセッティング
100年カレンダーの作り方をぐぐって、ここにたどり着く人のために、
Pythonのセッティングの仕方を書いておきます。

「Thonny」をぐぐります。そして、Thonnyをインストールしてください。
そのあと、起動したら、「Tools」の「Open System Shell...」を使って、コマンドを打ち込みます。
pip install openpyxl PySinpleGUI
「pip」でできなかった場合は、「pip」のところを「pip3」に変えます。
あとは、このブログ後半にある緑色で書かれている文字の羅列(ソースコード)を、
Thonnyにコピペして、F5キーをおして実行します。

LibreOfficeのセッティング
ぐぐってインストールします。
Pythonが作り出したxlsxファイル(エクセルファイル)を開くのに使います。
LibreOfficeで標準的なodsファイルは今回使いません・・・使えません。

やったこと
レイアウト作るために、寸法を計算して、実際にシートを作って、
変更を加えたところをなるべく忠実に、Pythonで再現しました。
あとは、カレンダー的な計算をして、それぞれのセルに埋めていく。
マクロの打ち込みはありません。

曜日を知る方法
曜日を知る公式として「ツェラーの公式」があるのですが、
正式な方法と、それを簡略化した ねとらぼ&QuizKnock方式があるようですが、
結局のところ、1900年代や2000年代と違うと、計算が合わなくなるので、
Wikiにのっていた、計算機用のツェラーの公式を使ってみるも、
出てくる数字が変なので、基準になる日付からどれだけ遡るかで判断します。
そのほうが確実だし・・・少なくとも、グレゴリオ暦を採用している1582年10月15までは。

うるう年になる年は、
年が4で割り切れるとうるう年だけど、
100で割り切れて400で割り切れない年は、うるう年じゃない。
ということは、1900年や2100年はうるう年じゃない。
こういう法則があります。
これに気を付けながら、
365日は、52週+1日。366日は52週+2日。
1年ずれるごとに、1または2ずつ曜日をずらしていけば、曜日はでるということになります。

その他
どういうわけか、openpyxlには列全体および行全体を中央に寄せることができません。
というよりも、そういうことがわかりました。
印刷を前提にするときは、けっこう普通に使う項目なので、実装してほしかった。。。
もう表を微調整するしかないじゃないの。
あと、AttributeError: 'tuple' object has no attribute 'value'という、
設定した覚えのないタプルに代入できないとかなんとかについては、
存在しないセル(「A0」とか「6」、とにかく0になるやつやアルファベットがない場合)を、
間違って設定している場合なので、
修正するしかありません。
計算が下手なので、ものすごくよく出しました。

そして仕様変更
本当は、未来から過去に遡れるカレンダーも作れるようにしたかったのですが、
よくわからないバグのせいで、過去から未来に進む分しか作れないようになってしまいました。
うん、くやしい。時間見てなんとかしたいと思いますが、きっと未来の私は忘れている。
あと、自分だけが使うのなら、むき出しのPythonから、
input命令を使ってどうにかするだけでもいいのですが、
みんなが使える形にするということで、GUIを実装しています。
意外とGUIはめんどうだったけど、
ほかのGUI化ライブラリよりは、PySimpleGUIは使いやすいです。
C言語とかの、複雑怪奇なGUIプログラムの存在を見ていると、特にそう感じます。

元号と祝日には対応していません
毎年変わる可能性がある春分と秋分以外の問題として、
祝日は、未来になればなるほど増えていきます。
昔、誰が海の日や山の日制定や、ハッピーマンデー法の奇襲攻撃を想像していたか。
未来の祝日なんて、預言者じゃないんだからわかりません。
そして天皇誕生日は元号ごとに変わり、記念日として定着するものもあれば、
しないこともあります。
これも、天皇家マニアじゃないから、全員の天皇候補者の誕生日を知りません。
天皇誕生日を予想できません。
そして天皇制廃止になる可能性だって、ゼロではないので。
未来のことはわかりませんので、祝日には対応しないことにしました。


特製ソース
何千年分もやろうとすると、エラーになるのは、避けられませんでした。
このソースをPythonでコピペして使うなら、先にコマンドを打ち込んでから実行します。
pip install openpyxl PySimpleGUI
「pip」でできなかった場合は、「pip」のところを「pip3」に変えます。
そのうち実行ファイルもうpします。
とりあえず、このへんが今の私の限界。

import openpyxl
from openpyxl.styles import Font
from openpyxl.styles import Alignment
import sys
import PySimpleGUI as sg

#A~Zを配列にする。A~Zは26文字
a2z=["","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
#ここからは追加させていく
i=1
j=1
while j<5:
    while i<=26:
        bufstr = a2z[j] + a2z[i]
        a2z.append(bufstr)
        i+=1
    i=1
    j+=1

#曜日を知る法則が通じないので、力技を使う。
#年,曜日
kako=[4000,0]
mirai=[-46,0]
kiroku=[2023,7]
#基準[2023,1,0,]

#関数化
def karendaa(kako,mirai,kiroku):
    #西暦か紀元前か
    def AD_BC(ahi):
        if ahi[0]<=0:
            return "B.C. "+str(abs(ahi[0])+1)
        else:
            return "A.D. "+str(ahi[0])
    
    #うるう判定
    def uruu_hantei(aieee):
        ughhh=False
        if aieee%4==0:
            ughhh=True
        if aieee%100==0 and aieee%400!=0:
            ughhh=False
        return ughhh

    #曜日を調整する関数
    def hiku_and_tasu(ahi):
        kiroku=[2023,7]

        while kiroku[0]!=ahi[0]:

            #なぜか過去に遡る分のうるう年だけ計算のタイミングが合わないので、
            #無理やりタイミングを合わせる
            if ahi[0]<2023 :
                if uruu_hantei(kiroku[0]-1)==True :
                    kiroku[1]-=2
                else:
                    kiroku[1]-=1
                kiroku[0]-=1
            #未来をたどる分には計算のタイミングが合う
            if ahi[0]>2023 :
                if uruu_hantei(kiroku[0])==True :
                    kiroku[1]+=2
                else:
                    kiroku[1]+=1
                kiroku[0]+=1
        
            if ahi[0]==2023:
                ahi[1]=0
                    
            if ahi[0]==kiroku[0]:
                while ahi[1]<0:
                    ahi+=7
                ahi[1] = kiroku[1]%7

    #曜日を調べる
    hiku_and_tasu(kako)

    #ブック作成
    book=openpyxl.Workbook()
    #シートを作成
    sheet=book['Sheet']
    ws=book.active

    #シートのレイアウトの設定
    #横向き
    ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE
    #フッターヘッター
    #ws.oddFooter.center.text = "&[Page] / &N ページ"
    #ws.oddHeader.center.text = "一覧表"
    #作ったものに合わせて縮小しない
    ws.sheet_properties.pageSetUpPr.fitToPage = False
    #余白(単位はインチ 1inch=2.54cm)
    ws.page_margins.top = 0.7874
    ws.page_margins.left = 0.7480
    ws.page_margins.bottom = 0.7874
    ws.page_margins.right = 0.7480
    #わからない設定。いるのだろうか?
    ws.print_options.horizontalCenterd = True
    ws.print_options.verticalCenterd = True

    #高さ、幅の大きさ変更
    #何ページ分作るかのカウンター
    peezi={"zissaino_kaunto":0,"peezisuu":0}

    #カレンダー作成用
    gatu=[None,31,28,31,30,31,30,31,31,30,31,30,31]
    koyomi=[None,"1 January","2 February","3 March","4 April","5 May","6 June","7 July","8 August","9 September","10 October","11 November","12 December"]
    sitiyou=["S","M","T","W","T","F","S"]
    nengappi=[kako[0],1,1,kako[1] ]

    #なぜかは知らないけど、
    #forやwhileで規定数回して高さを変更することができない
    #どういうわけか、全部指定することはできる。
    sheet.row_dimensions.width = 16.2857

    def seru_no_reiauto():
        #お目当ての年の1月1日の曜日を取得しなければならなくなった
        
        buf_array=[ nengappi[0] ,0]
    
        m=0
        while m<2:
            k=0
            while k<6:
            
                #列の幅を変更(ABC・・・)1≒0.1958cm
                j=0
                i=1
                while j<7:
                    while i<=7:
                        sheet.column_dimensions[ a2z[i+j*8] ].width = 2.84
                        i+=1
                    i=1
                    j+=1
                    if j!=6:
                        sheet.column_dimensions[ a2z[j*8] ].width = 0.82
                        
                #行の高さを変更(123・・・)1≒0.035cm
                buf_int = (32*abs(peezi["zissaino_kaunto"]))+1
                buf_str = ("A{0}:AU{1}").format(buf_int , buf_int)
                sheet.row_dimensions[buf_int].height = 22.8571
        
                #セルの書式
                so1=Font(size=14,bold=True)
                so2=Font(size=11,bold=True,color='ff000000')
                so3=Font(size=11,bold=True,color='ffff0011')
                so4=Font(size=11,bold=True,color='ff2200ff')
                so5=Font(size=11,bold=False,color='ff000000')
                so6=Font(size=11,bold=False,color='ffff0011')
                so7=Font(size=11,bold=False,color='ff2200ff')
                align = Alignment(horizontal='center', vertical='center')
    
                #セルの書式設定と一部セル埋め
                ws["A"+str(abs(peezi["zissaino_kaunto"])*32+1)].font=so1
                ws["A"+str(abs(peezi["zissaino_kaunto"])*32+1)].alignment=align
                ws["A"+str(abs(peezi["zissaino_kaunto"])*32+1)]=AD_BC(nengappi)
                #セルの結合
                sheet.merge_cells(buf_str )
        
                #月
                buf_str=a2z[8*k+1]+str(abs(2+(16*(m+peezi["peezisuu"]*2))))
                buf_str2=a2z[8*k+7]+str(abs(2+(16*(m+peezi["peezisuu"]*2))))
                sheet.merge_cells(buf_str+":"+buf_str2 )

                ws[buf_str].font=so2
                ws[buf_str].alignment=align
                ws[buf_str]=koyomi[nengappi[1] ]
                #曜日のセル書式
                buf_str = a2z[8*k+1] +str(abs( 3+(16*(m+peezi["peezisuu"]*2))))
                ws[buf_str].font=so3
                ws[buf_str].alignment=align
                buf_str2 = a2z[8*k+7] +str(abs( 3+(16*(m+peezi["peezisuu"]*2)) ))
                ws[buf_str2].font=so4
                ws[buf_str2].alignment=align
                i=2
                while i<=6:
                    ws[ a2z[ 8*k + i]+str(abs( 3+(16*(m+peezi["peezisuu"]*2))) )].font=so2
                    ws[ a2z[ 8*k + i]+str(abs( 3+(16*(m+peezi["peezisuu"]*2))) )].alignment=align
                    i+=1
                #七曜
                i=1
                while i<=7:
                    ws[ a2z[ 8*k + i ]+str(abs( 3+(16*(m+peezi["peezisuu"]*2)) ))]=sitiyou[i-1]
                    i+=1
                #カレンダー部分の色分け用のセル設定
                i=4
                while i<=9:
                    buf_str = a2z[ 8*k+1 ] + str(abs( i+(16*(m+peezi["peezisuu"]*2)) ))
                    ws[buf_str].font = so6
                    ws[buf_str].alignment=align
                    i+=1
    
                i=4
                while i<=9:
                    buf_str = a2z[ 8*k+1 +6 ] + str(abs( i+(16*(m+peezi["peezisuu"]*2))) )
                    ws[buf_str].font=so7
                    ws[buf_str].alignment=align
                    i+=1
                j=0
                i=4
                while j<5:
                    while i<=9:
                        buf_str = a2z[ 8*k + j+2 ] + str(abs( i+(16*(m+peezi["peezisuu"]*2)) ))
                        ws[buf_str].font=so5
                        ws[buf_str].alignment=align
                        i+=1
                    i=4
                    j+=1
                j=0
            
                #月に応じた日数を取得する
                buf_int2 = gatu[nengappi[1] ]
                uruu=False
                if nengappi[0]%4 == 0:
                    uruu=True
                if nengappi[0]%100==0 and nengappi[0]%400!=0:
                    uruu=False
                if uruu==True and nengappi[1]==2:
                    buf_int2=29
    
                #日数に応じてセルを埋める
                i=1
                hiku_and_tasu(buf_array)
                guhehe=abs(4+(16*(m+peezi["peezisuu"]*2)))
                if nengappi[1]==1:
                    nengappi[3]=buf_array[1]
            
                while i<=buf_int2:
                    buf_str = a2z[ k*8 + abs(nengappi[3])+1] + str(guhehe)
                    ws[buf_str]=i
                    nengappi[3]+=1
                    i+=1
                    if nengappi[3]>=7:
                        nengappi[3]-=7
                        guhehe+=1
                k+=1
                nengappi[1]+=1
                if nengappi[1]>12:
                    nengappi[1]-=12
            k=0
            m+=1
        m=0
        
         #暦を12個作ったら年月日を移動させる
        if kako[0]>mirai[0]:
            nengappi[0]-=1
        else:
            nengappi[0]+=1

    #カウント用
    while abs(peezi["peezisuu"]) <= abs(mirai[0]-kako[0]):
        seru_no_reiauto()
        if kako[0]>mirai[0]:
            peezi["peezisuu"]-=1
            peezi["zissaino_kaunto"]+=1
        else:
            peezi["peezisuu"]+=1
            peezi["zissaino_kaunto"]+=1

    #シートの中身

    #aaa="あーん、ドーナツが食べたいよう。"
    #bbb="222"
    #ws=book.active
    #ws["A1"]=aaa
    #ws["B2"]=bbb

    #シートの名前を変える
    #sheet.title='シートン博士'

    #保存
    if kako[0]<=0:
        buf_str1="BC"+str(abs(kako[0]-1))
    else:
        buf_str1=str(kako[0])

    if mirai[0]<=0:
        buf_str2="BC"+str(abs(mirai[0]-1))
    else:
        buf_str2=str(mirai[0])
    
    if kako[0]==mirai[0]:
        buf_str=("{0}.xlsx".format(buf_str2))
    else:
        buf_str=("{0}-{1}.xlsx".format(buf_str1,buf_str2))
    #ファイル名をつけてエクセルファイルを保存
    book.save(buf_str)

def ripureesu(bstr):
    bstr=bstr.replace("0","0")
    bstr=bstr.replace("1","1")
    bstr=bstr.replace("2","2")
    bstr=bstr.replace("3","3")
    bstr=bstr.replace("4","4")
    bstr=bstr.replace("5","5")
    bstr=bstr.replace("6","6")
    bstr=bstr.replace("7","7")
    bstr=bstr.replace("8","8")
    bstr=bstr.replace("9","9")
    bstr=bstr.replace("年","")
    bstr=bstr.replace("AD","")
    bstr=bstr.replace("A.D.","")
    bstr=bstr.replace("ad","")
    bstr=bstr.replace("a.d.","")
    bstr=bstr.replace("紀元後","")
    bstr=bstr.replace("キリスト歴","")
    bstr=bstr.replace("西暦","")
    bstr=bstr.replace("AD","")
    bstr=bstr.replace("A.D.","")
    bstr=bstr.replace("あD","")
    bstr=bstr.replace("あ。D。","")
    bstr=bstr.replace(" ","")
    bstr=bstr.replace(" ","")
    return bstr

def mainasu1(bstr):
    if ("B.C." in bstr)==True or \
     ("BC" in bstr)==True or \
     ("b.c." in bstr)==True or \
     ("bc" in bstr)==True or \
     ("BC" in bstr)==True or \
     ("B.C." in bstr)==True or \
     ("bc" in bstr)==True or \
     ("b。c。" in bstr)==True or \
     ("紀元前" in bstr)==True :
        return True
    else:
        return False

def kigenzen_kesu(bstr):
    bstr=bstr.replace("B.C.","")
    bstr=bstr.replace("BC","")
    bstr=bstr.replace("b.c.","")
    bstr=bstr.replace("bc","")
    bstr=bstr.replace("BC.","")
    bstr=bstr.replace("BC","")
    bstr=bstr.replace("b。c。","")
    bstr=bstr.replace("bc","")
    bstr=bstr.replace("紀元前","")
    return bstr

sg.theme("dark gray 1")
layout=[
    [sg.Text("これは過去から未来まで、指定したカレンダーの\n\
エクセルファイルを作るソフトです。\n\
過去と未来の欄に西暦を入れてね。\n\
紀元前は「BC」を入れるかマイナス数字で。")],
    [sg.Text()],
    [sg.Text("       過去?(半角数字で)")],
    [sg.Input(key="1")],
    [sg.Text("       未来?(半角数字で)")],
    [sg.Input(key="2")],
    [sg.Text()],
    [sg.Text("         "),sg.Button("作る")],
    [sg.Text(key="-mikotoba-")]]
window = sg.Window('カレンダーを何百年分も作る機', layout )

buf_bool=False
while True:
    event, values = window.read()
    if event == sg.WIN_CLOSED:
        break
    if event == '作る':
        try:
            str1=values["1"]
            str2=values["2"]
            kako[0]=int(str1)
            mirai[0]=int(str2)
            buf_bool=True
        except :
            if str1=="" or str2=="":
                window['-mikotoba-'].update('        空欄とかだめ~!')
                buf_bool=False
            else:
                try:
                    str1=ripureesu(str1)
                    str2=ripureesu(str2)
                    boo1=mainasu1(str1)
                    boo2=mainasu1(str2)
                    str1=kigenzen_kesu(str1)
                    str2=kigenzen_kesu(str2)
                    
                    kako[0]=int(str1)
                    mirai[0]=int(str2)
                    if boo1==True:
                        kako[0]*=-1
                        kako[0]+=1
                        boo1=False
                    if boo2==True:
                        mirai[0]*=-1
                        mirai[0]+=1
                        boo2=False
                    buf_bool=True
                except:
                    window['-mikotoba-'].update('    いや~ん! もう~! いけず~!')
                    window['1'].update('')
                    window['2'].update('')
                    buf_bool=False

        if kako[0]>mirai[0]:
            buf=kako[0]
            kako[0]=mirai[0]
            mirai[0]=buf
        if buf_bool==True:
            window['-mikotoba-'].update('        作るよ~!')
            karendaa(kako,mirai,kiroku)
            buf_bool=False
            window['-mikotoba-'].update('          できた~!')

window.close()

その他の参考資料