目的
ある月のカレンダーを表示させるプログラムを実装する。
仕様
- 日本でグレゴリオ暦が正式採用された1873年1月1日以降(明治6年)の日付を計算する
- ひと月単位のカレンダーを表示するためには1日の曜日を求める必要がある
- 2000/1/1(土曜日)を基準としてカレンダーにしたい月の1日の曜日を計算する
- 2000年以降はそこから曜日を求めたい日付まで何日経過したか
- 1999年以前はそこから何日遡るか
- つまり基準日から着目する日付までの日数から曜日を計算する
ソースコード
#include <stdio.h>
// 閏年判定関数
// 4で割り切れてかつ100で割り切れない、または400で割り切れる年は閏年
int isLeapYear(int year){
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
// 引数の日付の曜日を計算する
// 戻り値は曜日番号 0:Sunday, 1:Monday, 2:Tuesday, 3:Wednesday, 4:Thursday, 5:Friday, 6:Saturday
// -> 2000/1/1からの日数を計算することにより曜日番号を求める
// (c.f. 2000/1/1 Saturday)
int calcDayOfWeek(int year, int month, int day){
int y, m;
// 基準とする2000/1/1からの日数
int totalDays = 0;
// 曜日番号 0:Sunday, 1:Monday, 2:Tuesday, 3:Wednesday, 4:Thursday, 5:Friday, 6:Saturday
int dayOfWeekNum;
// 各月の日数(平年), 月と要素番号を合わせるために0番目に0を格納する
int daysInMonth[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
if(year < 2000){
// 1999年以前のカレンダーを計算する場合
// この場合は1999/12/31から求める日付まで遡るように計算する
// 例として1990/5/5の曜日を求める場合を考える
// 1999~1991までの日数を足す
for(y = 1999; y > year; y--){
// 閏年ならば1年366日を足す
if (isLeapYear(y)){
totalDays += 366;
}else{
totalDays += 365;
}
}
// yearが閏年なら2月を29日に設定する
if (isLeapYear(year)) daysInMonth[2] = 29;
// 1990年の12月から6月までの日数の合計を求める
for(m = 12; m > month; m--){
totalDays += daysInMonth[m];
}
// 5月末から5/5までの日数を加える
totalDays += daysInMonth[month] - day + 1;
dayOfWeekNum = 6 - totalDays%7;
}else{
// 2000年以降のカレンダーを計算する場合
// 2000/1/1
for(y = 2000; y < year; y++){
// yが閏年ならば1年366日を足す
if (isLeapYear(y)){
totalDays += 366;
}else{
totalDays += 365;
}
}
// yearが閏年なら2月を29日に設定する
if (isLeapYear(year)) daysInMonth[2] = 29;
for(m = 1; m < month; m++){
totalDays += daysInMonth[m];
}
totalDays += day - 1;
// 基準日である2000/1/1の曜日番号も足す
totalDays += 6;
dayOfWeekNum = totalDays%7;
}
return (dayOfWeekNum);
}
int main(void){
int year, month;
// 年の入力
printf("年を入力: ");
scanf("%d", &year);
// 月の入力
printf("月を入力: ");
scanf("%d", &month);
if(year < 1873){
printf("Only compatible from 1873.\n");
return(0);
}
if(month < 1 || 12 < month){
printf("input the month in 1-12.\n");
return(0);
}
// 各月の日数(平年), 月と要素番号を合わせるために0番目に0を格納する
int daysInMonth[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
// yearが閏年なら2月を29日に設定する
if (month == 2 && isLeapYear(year)) daysInMonth[2] = 29;
// カレンダーの見出し
printf("\n%d年 %d月のカレンダー\n", year, month);
printf("日 月 火 水 木 金 土\n");
// 指定月の1日が何曜日かを計算する
int firstDay = calcDayOfWeek(year, month, 1);
// カレンダー最初の行に空白を出力
for (int i = 0; i < firstDay; i++) {
printf(" "); // 空白3文字分
}
// 各日の出力
for (int day = 1; day <= daysInMonth[month]; day++) {
printf("%2d ", day); // 2桁表示で揃える
// 土曜ごとに改行
if ((firstDay + day) % 7 == 0) printf("\n");
}
printf("\n"); // 最後に改行
return(0);
}
出力結果
年を入力: 2026
月を入力: 1
2026年 1月のカレンダー
日 月 火 水 木 金 土
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
年を入力: 1990
月を入力: 5
1990年 5月のカレンダー
日 月 火 水 木 金 土
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
課題
- 各月の日数を格納した配列daysInMonth[]が二度定義されている(main関数とcalcDayOfWeek関数)
- 入力した月の日数を返す関数を新規に作成することで解決
- isLeapYear関数は閏年か否や(true/false)をreturnするため、呼び出し元でその戻り値に基づいて着目している年が365日なのか366日なのかを割り振らないといけない(二度手間なプロセス)
- ある年が閏年 or Notではなく、365日あるいは366日なのかを返す関数にしたい