6
4

More than 3 years have passed since last update.

C言語でカレンダー

Posted at

はじめに

久しぶりの投稿です。前作を見てくださったみなさん、ありがとうございます。今後ともよろしくお願いします。
今回はC言語を用いたカレンダー作りです。WSLのUbuntuでcalコマンドの存在を知り、Windowsでも使いたいと思い、この記事を書くに至りました。なんちゃってcalコマンドですが...

条件

今回の条件は以下のとおりです。

  • コマンドライン引数で年月を取得
  • 日曜始まり

ソースコード

cal.c
#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月1日から2020年1月1日までのgap:2508日
  2. 2020年1月1日から2020年11月1日までのgap:25日
  3. (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関数を呼び出します。

出力の順番

  1. name[3*(month-1)+i] (0 <= i <= 2) で取得できる月名3文字
  2. printf(" %-4d\n", year) で西暦年の右詰め(4ケタ)
  3. パディング用スペースを (gap+1) % 7 回(+1を+0に変更すれば月曜始まり)
  4. 1, 2, 3, ..., days[month-1] で日付
  5. 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 

おわりに

このプログラムが、みなさんの役にたてば幸いです。ご一読ありがとうございました。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4