今回は大学の講義の課題でありがちなカレンダーを表示するプログラムの解説をしたいと思います。今回はC言語でコードを書いて、カレンダーはコンソールに表示するようにします。
⬇︎こんな感じです

全体のコードも下に貼ってあるので参考にしてください!
カレンダー生成にあたってのポイント
今回のコードのポイントをザーッと書き出してみます。
- 一週間分表示させたら改行する。
- 三ヶ月分のカレンダーを横並びで表示する。
- 年と月によって1日が何曜日かが変わる。
- その年が閏年かどうかを判別しなければならない。
って感じですかね。
特に三ヶ月分を横並びで表示させるのは工夫が必要なのでしっかりと確認してください。
コード実装
ここからコードの実装です。
zeller(ツェラー)の公式の関数
まずはzellerの公式を計算する関数から実装します。

zellerの公式とは?
まあ普通は聞いたことないですよね。
zellerの公式っていうのは特定の年月日の曜日を特定するやばい公式です。
以下がzellerの公式です。

なんでこの公式で曜日が特定できるのかは分かりませんがせっかくなのでコードで使わせてもらいましょう。
今回は各月のカレンダーの1日の曜日の情報だけが欲しいので、上の公式のd(日)の部分は1としてコードを組んでいきます。
int zeller(int y, int m){
if(m==1){
m = 13;
y--;
}else if(m==2){
m = 14;
y--;
}
int X;
X = (y + (y/4) - (y/100) + (y/400) + ((13*m + 8)/5) + 1)%7;
return X;
}
引数のyは年、mは月です。
中身は上の公式のまんまなので理解できると思います。
曜日に対応した値を返り値としています。
閏年かどうかを判定する関数
次は閏年かどうかを判定する関数です。
閏年は次の規則に従っています。
- 西暦が4で割り切れる年は閏年
- 西暦が100で割り切れるが、400で割り切れない年は閏年
この条件を元にコードを書いていきます。
int leap_year(int y){
if( y%400==0){
return 29;
}
else if(y%100==0){
return 28;
}
else if(y%4==0){
return 29;
}
else{
return 28;
}
}
引数のyは年です。
そのyに従って計算して閏年なら29、平年なら28を返します。
後々のためにこのような返り値としています。
一ヶ月分のカレンダーを計算して配列に格納する関数
ここから少しややこしいですね。
上でも書いた通り三ヶ月分のカレンダーを横並びで表示するために工夫をします。
C言語ではコンソールに出力するときは下へ下へと表示されていくのが基本です。

ということは横並びで表示するためには三ヶ月分のカレンダーを並行で計算して、順に表示するのを繰り返す必要があるってこと?
まあ適当に組もうと思うとそうなりますね
↓(三ヶ月分を並行で考える場合のイメージ図)

だけど、この方法じゃ相当計算がめんどくさいのでやめときます。
やっぱり一ヶ月分を一気に計算した方が良い。。。
ということで一ヶ月分を計算して、一旦配列に格納する方法で考えていきます。
一ヶ月分を計算して配列に格納。それを3ヶ月分行ってから、表示する。という流れですね
ここでは計算と格納を行うコードを考えます。
void month_cal(int y, int m, int a[6][7]){
int max[12] = {31,leap_year(y),31,30,31,30,31,31,30,31,30,31};
int count = 1;
for(int s=zeller(y,m); s<7; s++){
a[0][s] = count;
count++;
}
for(int i=1; i<6; i++){
for(int j=0; j<7; j++){
if(count <= max[m-1]){
a[i][j] = count;
count++;
}else{
break;
}
}
}
}
引数のyは年、mは月、配列のaはカレンダーを格納する配列です。
配列が6×7の二次元配列なのは以下のように日数を格納するからです。

1日より前とその月の最後の日より後は全て0とします。
そのために引数で6×7の配列aを受け取る前に、配列aは全て0で初期化されている状態とします。
ここからは関数本文についてです。
最初に定義している配列maxは各月の最後の日を格納しています。
2月については閏年か平年かで変わってくるので関数leap_year()にyを渡したものとしています。(返り値がそのまま最後の日になっているのでおk)
次のfor文はカレンダーの1行目についてです。
int count = 1;
for(int s=zeller(y,m); s<7; s++){
a[0][s] = count;
count++;
}
for文はzeller()の値から6までで回します。上の画像で示した通りzellerの公式で返ってくる値と、定義した配列の添字番号が同じなのでzeller()から始めればちょうどぴったしというわけです。
そしてその中でa[0][s] = countとして日付を代入していきます。
(countはforの前に1で定義してfor文が回るたびにインクリメントする)
そして2つ目のfor文です
for(int i=1; i<6; i++){
for(int j=0; j<7; j++){
if(count <= max[m-1]){
a[i][j] = count;
count++;
}else{
break;
}
}
}
ここはfor文が二重になっていますが簡単ですね。
配列aの2行目から7行目までに日付を代入していってます。
ただ日付を代入する前にif文でcountがその月の最後の日を超えていないかを判定して、超えていなければ代入、超えていればbreakでfor文を抜け出すようにしています。
これで一ヶ月分の情報を配列に格納できました。
三ヶ月分のカレンダーを横並びで表示する関数
ここが一番ややこしい部分。
上で配列に格納したカレンダーを表示していきます。
とりあえずコードです。
void show_three_month(int y, int l){
int callender[3][6][7] = {0};
for(int i=0; i<3; i++){
month_cal(y, 3*l+1+i, callender[i]);
}
for(int i=0; i<3; i++){
printf(" %3d ",3*l+1+i);
}
printf("\n");
for(int i=0; i<3; i++){
printf(" S M T W T F S ");
}
printf("\n\n");
for(int i=0; i<6; i++){
for(int j=0; j<3; j++){
for(int k=0; k<7; k++){
if(callender[j][i][k]==0){
printf(" ");
}else{
printf("%3d",callender[j][i][k]);
}
}
printf(" ");
}
printf("\n");
}
}
引数のyは年で、lはコンソールに表示する際の行数です。
具体的には3ヶ月分を4行表示するのでlは0,1,2,3です。(配列的に0行目と数えてます)
callender[3][6][7]はカレンダー三ヶ月分を格納する3次元配列です。
次のfor文でここに格納してます。
for(int i=0; i<3; i++){
month_cal(y, 3*l+1+i, callender[i]);
}
month_calに月を渡す際は3xl+1+iとしてます。これで該当する月になってます。
そして次はカレンダーの月と曜日の表示です。
for(int i=0; i<3; i++){
printf(" %3d ",3*m+1+i);
}
printf("\n");
for(int i=0; i<3; i++){
printf(" S M T W T F S ");
}
printf("\n\n");
ここでは最初のforで月を三ヶ月分横並びに表示して改行、
次のforでは曜日を三ヶ月分横並びに表示して改行してます。
ちなみに%3dっていうのは半角で3文字分確保して右詰で表示ってことです。 (カレンダーを整えるため)
次のfor文は表示に関する部分です。
for(int i=0; i<6; i++){
for(int j=0; j<3; j++){
for(int k=0; k<7; k++){
if(callender[j][i][k]==0){
printf(" ");
}else{
printf("%3d",callender[j][i][k]);
}
}
printf(" ");
}
printf("\n");
}
一番外側のforは行を回すため、
2番目のforは一週間の表示をを3ヶ月分回すため、
一番内側のforは一週間を表示するためです。
↓イメージ図

ややこしいですけど、
一週間表示したら隣の月、それを3回続けたら改行してまた一番左の月から同じことを繰り返す。
ってかんじですね。これで三ヶ月を横並びで表示することを達成してます。
表示の際はまずその値が0かどうかを判定。
もし0なら半角で空白を3つ分表示する。そうでないならその数字を半角三文字確保した中で右詰で表示することにしてます。
一週間分表示しきったら半角で空白を3つ分表示してます。これは隣の月との間の空白です。
一週間を三ヶ月分表示し切ったら改行してます。
これでおkですね。
main関数
int main(int argc, const char * argv[]) {
int y;
printf("西暦何年のカレンダーを表示しますか?:");
scanf("%d",&y);
for(int i=0; i<4; i++){
show_three_month(y, i);
}
return 0;
}
ここはもう見た通りですね。
西暦を受け取ってそれを元に上で作ったshow_three_monthを4列表示してます。
まとめ
上でも書いた通り大学の課題ではありがちだと思うので是非参考に使ってください。
以下全体のコードです。
それでは
全体のコード
#include <stdio.h>
int zeller(int y, int m){
if(m==1){
m = 13;
y--;
}else if(m==2){
m = 14;
y--;
}
int X;
X = (y + (y/4) - (y/100) + (y/400) + ((13*m + 8)/5) + 1)%7;
return X;
}
int leap_year(int y){
if( y%400==0){
return 29;
}
else if(y%100==0){
return 28;
}
else if(y%4==0){
return 29;
}
else{
return 28;
}
}
void month_cal(int y, int m, int a[6][7]){
int max[12] = {31,leap_year(y),31,30,31,30,31,31,30,31,30,31};
int count = 1;
for(int s=zeller(y,m); s<7; s++){
a[0][s] = count;
count++;
}
for(int i=1; i<6; i++){
for(int j=0; j<7; j++){
if(count <= max[m-1]){
a[i][j] = count;
count++;
}else{
break;
}
}
}
}
void show_three_month(int y, int l){
int callender[3][6][7] = {0};
for(int i=0; i<3; i++){
printf(" %3d ",3*l+1+i);
}
printf("\n");
for(int i=0; i<3; i++){
month_cal(y, 3*l+1+i, callender[i]);
printf(" S M T W T F S ");
}
printf("\n\n");
for(int i=0; i<6; i++){
for(int j=0; j<3; j++){
for(int k=0; k<7; k++){
if(callender[j][i][k]==0){
printf(" ");
}else{
printf("%3d",callender[j][i][k]);
}
}
printf(" ");
}
printf("\n");
}
}
int main(int argc, const char * argv[]) {
int y;
printf("西暦何年のカレンダーを表示しますか?:");
scanf("%d",&y);
for(int i=0; i<4; i++){
show_three_month(y, i);
}
return 0;
}
コメント