#はじめに
久しぶりの投稿です。前作を見てくださったみなさん、ありがとうございます。今後ともよろしくお願いします。
今回はC言語を用いたカレンダー作りです。WSLのUbuntuでcalコマンドの存在を知り、Windowsでも使いたいと思い、この記事を書くに至りました。なんちゃってcalコマンドですが...
#条件
今回の条件は以下のとおりです。
- コマンドライン引数で年月を取得
- 日曜始まり
#ソースコード
#include <stdio.h>
#include <stdlib.h>
int check_leap(int year){
if (year % 4 != 0) return 0;
else if (year % 100 != 0) return 1;
else if (year % 400 != 0) return 0;
else return 1;
}
void return_error(int status, int num){
switch (status){
case 0: fprintf(stderr, "Usage: cal [month] [year]"); break;
case 1: fprintf(stderr, "cal: year \'%d\' is not in range 1..9999", num); break;
case 2: fprintf(stderr, "cal: month \'%d\' is not in range 1..12", num); break;
default: fprintf(stderr, "cal: unknown error");
}
exit(1);
}
int print_cal(int month, int year){
int gap = 0, i, cnt;
const int days[12] = {31, 28+check_leap(year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const char name[37] = "JanFebMarAprMayJunJulAugSepOctNovDec";
for (i = 1; i < year; i++) gap += 1+check_leap(i);
for (i = 0; i < month-1; i++) gap += days[i]-28;
printf(" ");
for (i = 0; i < 3; i++) printf("%c", name[3*(month-1)+i]);
printf(" %-4d\n", year);
printf("Su Mo Tu We Th Fr Sa\n");
for (cnt = 0; cnt < (gap+1)%7; cnt++) printf(" ");
for (i = 1; i <= days[month-1]; i++, cnt++){
if (cnt % 7 == 0 && cnt != 0) printf("\n");
printf("%2d ", i);
}
printf("\n");
return 0;
}
int main(int argc, char *argv[]){
int month, year;
if (argc < 3) return_error(0, 0);
month = atoi(argv[1]);
year = atoi(argv[2]);
if (year < 1 || year > 9999) return_error(1, year);
if (month < 1 || month > 12) return_error(2, month);
print_cal(month, year);
return 0;
}
#解説
##check_leap関数
引数の年がうるう年(leap year)かを調べる関数です。
西暦n年がうるう年であるための条件は、
(n % 4 == 0 NOT n % 100 == 0) OR n % 400 == 0
です。返り値はこの後使います。
##return_error関数
トラブルがあった際に呼び出す関数です。それだけです。
メッセージは標準エラー出力に出されます。
##print_cal関数
このプログラムのコアです。
###使用変数
- days:毎月の日数を管理します。check_leap関数の戻り値はdays[1](2月)に加算されます。
- gap: 365 = 7 * 52 + 1 より、1年で元日は1日ずれます(うるう年は2日)。西暦1年から西暦n-1年までのズレを足し合わせることで、西暦n年の元日が西暦1年のそれから何曜日分ずれているのかを計算できます。また、西暦n年m月1日と元日のズレも計算します(なお西暦1年の元日は月曜日)。
- name:各月名の3文字をまとめています。name[36]には自動でヌル文字が入り、これで確認できます。
if (name[36] == '\0') printf("NULL!\n");
- cnt:出力の際、7日ごとに改行するためのカウンター変数です。
###具体例
2020年11月の場合
- 1年1月1日から2020年1月1日までのgap:2508日
- 2020年1月1日から2020年11月1日までのgap:25日
- (2508 + 25) % 7 = 6 より、2020年11月1日は1年の元日から6日のずれ、ゆえに日曜日
main関数
コマンドライン引数の管理を行います。
argcは引数の数、argvは各引数の先頭文字のポインタを示します。なお、argcはプログラム名も含みます。例えば "cal 11 2020" と入力した場合、argc = 3 です。
stdlib.h 内のatoi関数で、argv[1](月)とargv[2](年)の文字列を整数型に変換し、条件を満たしていれば、print_cal関数を呼び出します。
##出力の順番
- name[3*(month-1)+i] (0 <= i <= 2) で取得できる月名3文字
- printf(" %-4d\n", year) で西暦年の右詰め(4ケタ)
- パディング用スペースを (gap+1) % 7 回(+1を+0に変更すれば月曜始まり)
- 1, 2, 3, ..., days[month-1] で日付
- cnt == 7 なら改行
これを繰り返します。
#使用例
> cal 11 2020
Nov 2020
Su Mo Tu We Th Fr Sa
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
#おわりに
このプログラムが、みなさんの役にたてば幸いです。ご一読ありがとうございました。