たまに曜日が表示されないときがあります。

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
みうけい
記事: 8
登録日時: 7年前

たまに曜日が表示されないときがあります。

#1

投稿記事 by みうけい » 7年前

こんばんは。いつもお世話になっています。
私は今、「今日」の日付を入力すると、「次の日」と「前の日」が表示されるプログラムをC言語で作っているのですが、ときたま曜日が表示されないときがあります。その原因を探っていただけないでしょうか。また、コードが長いので、短くする方法を教えていただければ幸いです。よろしくお願いします。

(例:正しく表示される場合)
年を入力してください。
1
月を入力してください。
1
日を入力してください。
1
今日は西暦1年1月1日SUNです。
次の日は西暦1年1月2日MONです。
前の日は紀元前1年12月31日SATです。

(例:正しく表示されない場合)
年を入力してください。
1
月を入力してください。
1
日を入力してください。
7
今日は西暦1年1月7日SATです。
次の日は西暦1年1月8日SUNです。
前の日は西暦1年1月6日です。(←ここが表示されない)

コードを貼っておきます。↓

コード:

#include <stdio.h>
#define Z "\aそんな日はありません。もう一度入力してください。\n"
void increment_date(int *y,int *m,int *d)/*日付を一日増やします*/
{
	switch(*m){
		case 1 :if(*d==31){*m=2;*d=1;}else{*d=*d+1;}break;
		case 2 :if(*y%4==0){if(*d==29){*m=3;*d=1;}else{*d=*d+1;}break;}
				else{if(*d==28){*m=3;*d=1;}else{*d=*d+1;}break;}
		case 3 :if(*d==31){*m=4;*d=1;}else{*d=*d+1;}break;
		case 4 :if(*d==30){*m=5;*d=1;}else{*d=*d+1;}break;
		case 5 :if(*d==31){*m=6;*d=1;}else{*d=*d+1;}break;
		case 6 :if(*d==30){*m=7;*d=1;}else{*d=*d+1;}break;
		case 7 :if(*d==31){*m=8;*d=1;}else{*d=*d+1;}break;
		case 8 :if(*d==31){*m=9;*d=1;}else{*d=*d+1;}break;
		case 9 :if(*d==30){*m=10;*d=1;}else{*d=*d+1;}break;
		case 10 :if(*d==31){*m=11;*d=1;}else{*d=*d+1;}break;
		case 11 :if(*d==30){*m=12;*d=1;}else{*d=*d+1;}break;
		case 12 :if(*d==31){*y=*y+1;*m=1;*d=1;}else{*d=*d+1;}if(*y==0){*y=1;}break;
	}
}
void decrement_date(int *y,int *m,int *d)/*日付を一日減らします*/
{
	switch(*m){
		case 1 :if(*d==1){*y=*y-1;*m=12;*d=31;}else{*d=*d-1;}if(*y==0){*y=-1;}break;
		case 2 :if(*d==1){*m=1;*d=31;}else{*d=*d-1;}break;
		case 3 :if(*y%4==0){if(*d==1){*m=2;*d=29;}else{*d=*d-1;}break;}
				else{if(*d==1){*m=2;*d=28;}else{*d=*d-1;}break;}
		case 4 :if(*d==1){*m=3;*d=31;}else{*d=*d-1;}break;
		case 5 :if(*d==1){*m=4;*d=30;}else{*d=*d-1;}break;
		case 6 :if(*d==1){*m=5;*d=31;}else{*d=*d-1;}break;
		case 7 :if(*d==1){*m=6;*d=30;}else{*d=*d-1;}break;
		case 8 :if(*d==1){*m=7;*d=31;}else{*d=*d-1;}break;
		case 9 :if(*d==1){*m=8;*d=31;}else{*d=*d-1;}break;
		case 10 :if(*d==1){*m=9;*d=30;}else{*d=*d-1;}break;
		case 11 :if(*d==1){*m=10;*d=31;}else{*d=*d-1;}break;
		case 12 :if(*d==1){*m=11;*d=30;}else{*d=*d-1;}break;
	}
}
int main(void)
{
	int a,b,c,d,e,f,date=0,i;
	int g[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
	char h[7][128]={"SAT","SUN","MON","TUE","WED","THU","FRI"};
	/*可能な西暦を設定*/
	printf("年を入力してください。\n");
	do{
		scanf("%d",&a);
		if(a==0){
			printf("\aそんな年はありません。もう一度入力してください。\n");
		}
	}while(a==0);
	/*可能な月を設定*/
	printf("月を入力してください。\n");
	do{
		scanf("%d",&b);
		if(b<1||b>12){
			printf("\aそんな月はありません。もう一度入力してください。\n");
		}
	}while(b<1||b>12);
	/*可能な日を設定*/
	printf("日を入力してください。\n");
	scanf("%d",&c);
	switch(b){
		case 1 :do{if(c<1||c>31){printf(Z);scanf("%d",&c);}}while(c<1||c>31);break;
		case 2 :if(a%4==0){do{if(c<1||c>29){printf(Z);scanf("%d",&c);}}while(c<1||c>29);break;}
				else{do{if(c<1||c>28){printf(Z);scanf("%d",&c);}}while(c<1||c>28);break;}
		case 3 :do{if(c<1||c>31){printf(Z);scanf("%d",&c);}}while(c<1||c>31);break;
		case 4 :do{if(c<1||c>30){printf(Z);scanf("%d",&c);}}while(c<1||c>30);break;
		case 5 :do{if(c<1||c>31){printf(Z);scanf("%d",&c);}}while(c<1||c>31);break;
		case 6 :do{if(c<1||c>30){printf(Z);scanf("%d",&c);}}while(c<1||c>30);break;
		case 7 :do{if(c<1||c>31){printf(Z);scanf("%d",&c);}}while(c<1||c>31);break;
		case 8 :do{if(c<1||c>31){printf(Z);scanf("%d",&c);}}while(c<1||c>31);break;
		case 9 :do{if(c<1||c>30){printf(Z);scanf("%d",&c);}}while(c<1||c>30);break;
		case 10 :do{if(c<1||c>31){printf(Z);scanf("%d",&c);}}while(c<1||c>31);break;
		case 11 :do{if(c<1||c>30){printf(Z);scanf("%d",&c);}}while(c<1||c>30);break;
		case 12 :do{if(c<1||c>31){printf(Z);scanf("%d",&c);}}while(c<1||c>31);break;
		}
	d=a;e=b;f=c;
	if(a%4==1){date=(a/4)*(365*4+1)+365*0;}
	if(a%4==2){date=(a/4)*(365*4+1)+365*1;}
	if(a%4==3){date=(a/4)*(365*4+1)+365*2;}
	if(a%4==0){date=(a/4)*(365*4+1)+365*3;}
	switch(b%12){
		case 1 :date+=c;break;
		case 2 :date+=c+g[1];break;
		case 3 :if(a%4==0){date+=c+g[1]+g[2]+1;}
				else{date+=c+g[1]+g[2];}break;
		case 4 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+1;}
				else{date+=c+g[1]+g[2]+g[3];}break;
		case 5 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+g[4]+1;}
				else{date+=c+g[1]+g[2]+g[3]+g[4];}break;
		case 6 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+1;}
				else{date+=c+g[1]+g[2]+g[3]+g[4]+g[5];}break;
		case 7 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+1;}
				else{date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6];}break;
		case 8 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+1;}
				else{date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7];}break;
		case 9 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8]+1;}
				else{date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8];}break;
		case 10 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8]+g[9]+1;}
				else{date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8]+g[9];}break;
		case 11 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8]+g[9]+g[10]+1;}
				else{date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8]+g[9]+g[10];}break;
		case 12 :if(a%4==0){date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8]+g[9]+g[10]+g[11]+1;}
				else{date+=c+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8]+g[9]+g[10]+g[11];}break;
	}
	/*曜日を特定*/
	switch(date%7){
		case 0:i=0;break;
		case 1:i=1;break;
		case 2:i=2;break;
		case 3:i=3;break;
		case 4:i=4;break;
		case 5:i=5;break;
		case 6:i=6;break;
	}
	if(a>0){
		printf("今日は西暦%d年%d月%d日%sです。\n",a,b,c,h[i]);
	}else{
		printf("今日は紀元前%d年%d月%d日%sです。\n",-a,b,c,h[i]);
	}
	increment_date(&a,&b,&c);
	decrement_date(&d,&e,&f);
	if(a>0){
		i++;
		printf("次の日は西暦%d年%d月%d日%sです。\n",a,b,c,h[i]);
	}else{
		i++;
		printf("次の日は紀元前%d年%d月%d日%sです。\n",-a,b,c,h[i]);
	}
	if(d>0){
		i=i-2;
		printf("前の日は西暦%d年%d月%d日%sです。\n",d,e,f,h[i]);
	}else{
		i=i-2;
		printf("前の日は紀元前%d年%d月%d日%sです。\n",-d,e,f,h[i]);
	}
	return 0;
}

box
記事: 2002
登録日時: 13年前

Re: たまに曜日が表示されないときがあります。

#2

投稿記事 by box » 7年前

hのiの値がいくつになっているか、printf()か何かで確認してみましょう。
0~6以外の値になっているような気がします。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

みうけい
記事: 8
登録日時: 7年前

Re: たまに曜日が表示されないときがあります。

#3

投稿記事 by みうけい » 7年前

原因が分かりました。
i=0のとき1と-1が生成されてしまうため、だめだと分かりました。
(本当は1と6が生成されないといけない)
ご回答ありがとうございました。

アバター
purin52002
記事: 235
登録日時: 7年前
連絡を取る:

Re: たまに曜日が表示されないときがあります。

#4

投稿記事 by purin52002 » 7年前

こんばんは
コード拝見しました。

まず曜日が表示されない不具合についてですが、131~137行目の前日の曜日を表示するところに問題があると思います。

コード:

if(d>0){
    i=i-2;
    printf("前の日は西暦%d年%d月%d日%sです。\n",d,e,f,h[i]);
}else{
    i=i-2;
    printf("前の日は紀元前%d年%d月%d日%sです。\n",-d,e,f,h[i]);
}
i=i-2とするとiが0未満になる恐れがあります。
するとhが変なところを見てしまうので表示がされないのかな、と思います。


次にコードに関してですが、

まず変数名をabcdefghiから変えたほうがいいかもしれません。
今回ぐらいのコードでしたら気にならないかもしれませんが、
変数名だけでその変数の役割がわかるようにしたほうがコードを書く側も見る側も助かります。

年月の入力は特に問題ないかと思います。
日付の入力ですが、かなり見づらいです。
コードはほぼ一緒なので一まとめにできそうです。
switch文でできることは配列でもできます。

コード:

do{
    if(c<1||c>g[b]){
        printf(Z);
        scanf("%d",&c);
    }
}while(c<1||c>g[b]);
こんな感じにまとめられそうです。(2月はもうちょっと工夫する必要がありそうですね)
変態ちっくですが関数ポインタを使えばもう少し短くなるかも、、、?

次の部分はちょっと読む気になれませんでした^^;
だから飛ばしますw

曜日を特定するコードもswitch文なしで書けそうです。

コード:

i=date%7;
次に日付を表示する部分ですが
if文の中に似たような部分があるので、それをif文の外に出しましょう。

コード:

char *seireki[] = {"西暦","紀元前"};
char *hyouji;
int nen;

if(a>0){
    hyouji=seireki[0];
    nen=a;
}else{
    hyouji=seireki[1];
    nen=-a;
    }   
printf("今日は%s%d年%d月%d日%sです。\n",hyouji,nen,b,c,h[i]);
    
increment_date(&a,&b,&c);
decrement_date(&d,&e,&f);

i=(i+1)%7;//iが7以上にならないようにする
//以下略
今日、明日、前日も文字列配列にすればfor文で回せそうですね。

increment_date、decrement_dateについてもちょっと読む気になれなかったのですが、
似たようなコードですので、おそらく一まとめにできると思います。

初心者の意見ですので話半分で聞いといてください^^;
オフトピック
コード書いてるうちに解決済みになっちゃいました^^;
申し訳ないorz
c++初心者を自負しています。
質問者さんには今後私にプログラミングを教えてくれるようにやさしく丁寧に教えるつもりです。ぎぶあんどていく^p^
回答者さんには精一杯感謝します。ぎぶおんりー^p^

みうけい
記事: 8
登録日時: 7年前

Re: たまに曜日が表示されないときがあります。

#5

投稿記事 by みうけい » 7年前

詳しくご説明ありがとうございます。
自分の改良すべき点が次々と浮かび出てきました。もっとc言語を学んで、簡潔で、かつ他の人が見やすく、理解しやすいコードを書けるよう心掛けます。また、後日、改良版コードを張りたいと思っています。
本当にありがとうございました。

かずま

Re: たまに曜日が表示されないときがあります。

#6

投稿記事 by かずま » 7年前

みうけいさんのプログラムでは、
2017年1月1日が SUN となって、これは正しいのですが、
2016年12月31日が TUE となって、これは間違っています。

また、現在の西暦はグレゴリオ暦で、閏年が4年に1回とは限りません。
詳細は Wikipedia などで調べてください。

ユリウス暦や紀元前を扱うのは面倒なので、グレゴリオ暦だけのコードを書いてみました。

コード:

#include <stdio.h>

int t[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const char *n[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

int dayofweek(int y, int m, int d)
{
    y -= m < 3;
    return (y + y/4 - y/100 + y/400 + ".\0\3\2\5\0\3\5\1\4\6\2\4"[m] + d) % 7;
}

int isleap(int y) { return y%4 == 0 && (y%100 != 0 || y%400 == 0); }

void increment_date(int *y, int *m, int *d)
{
    int z = t[*m];
    if (*m == 2 && isleap(*y)) z = 29;
    if (++*d > z) {
        *d = 1;
        if (++*m > 12) {
            *m = 1; ++*y;
        }
    }
}

void decrement_date(int *y, int *m, int *d)
{
    if (--*d < 1) {
        if (--*m < 1) {
            --*y; *m = 12; *d = 31;
        }
        else {
            *d = t[*m];
            if (*m == 2 && isleap(*y)) *d = 29;
        }
    }
}

int main(void)
{
    int y, m, d, w, z, y2, m2, d2;

    puts("年を入力してください。");
    while (1) {
        if (scanf("%d", &y) != 1) return 1;
        if (y > 1582) break;
        puts("1582 より大きい年を入力してください。");
    }
    puts("月を入力してください。");
    while (1) {
        if (scanf("%d", &m) != 1) return 1;
        if (m >= 1 && m <= 12) break;
        puts("正しい月を入力してください。");
    }
    z = t[m];
    if (m == 2 && isleap(y)) z = 29;
    puts("日を入力してください。");
    while (1) {
        if (scanf("%d", &d) != 1) return 1;
        if (m >= 1 && m <= z) break;
        puts("正しい日を入力してください。");
    }
    w = dayofweek(y, m, d);
    printf("今日は西暦%d年%d月%d日%sです。\n", y, m, d, n[w]);
    y2 = y, m2 = m, d2 = d;
    increment_date(&y2, &m2, &d2);
    printf("次の日は西暦%d年%d月%d日%sです。\n", y2, m2, d2, n[(w+1)%7]);
    decrement_date(&y, &m, &d);
    printf("前の日は西暦%d年%d月%d日%sです。\n", y, m, d, n[(w+6)%7]);
    return 0;
}

かずま

Re: たまに曜日が表示されないときがあります。

#7

投稿記事 by かずま » 7年前

訂正です。60行目の
if (m >= 1 && m <= z) break; は
if (d >= 1 && d <= z) break; の間違いです。

月の最終日が使うところが 3個所あるので、一つの関数 last にまとめてみました。

コード:

#include <stdio.h>

int t[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const char *n[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

int isleap(int y) { return y%4 == 0 && (y%100 != 0 || y%400 == 0); }

int last(int y, int m) { return t[m] + (m == 2 && isleap(y)); }

int dayofweek(int y, int m, int d)
{
    y -= m < 3;
    return (y + y/4 - y/100 + y/400 + ".\0\3\2\5\0\3\5\1\4\6\2\4"[m] + d) % 7;
}

void increment_date(int *y, int *m, int *d)
{
    if (++*d > last(*y, *m)) {
        *d = 1;
        if (++*m > 12) *m = 1, ++*y;
    }
}

void decrement_date(int *y, int *m, int *d)
{
    if (--*d < 1)
        if (--*m < 1) --*y, *m = 12, *d = 31;
        else *d = last(*y, *m);
}

int main(void)
{
    int y, m, d, w, y2, m2, d2;

    for (; puts("年を入力してください。") != EOF; printf("1582 より大きい")) {
        if (scanf("%d", &y) != 1) return 1;
        if (y > 1582) break;
    }
    for (; puts("月を入力してください。") != EOF; printf("正しい")) {
        if (scanf("%d", &m) != 1) return 1;
        if (m >= 1 && m <= 12) break;
    }
    for (; puts("日を入力してください。") != EOF; printf("正しい")) {
        if (scanf("%d", &d) != 1) return 1;
        if (d >= 1 && d <= last(y, m)) break;
    }
    w = dayofweek(y, m, d);
    printf("今日は西暦%d年%d月%d日%sです。\n", y, m, d, n[w]);
    y2 = y, m2 = m, d2 = d;
    increment_date(&y2, &m2, &d2);
    printf("次の日は西暦%d年%d月%d日%sです。\n", y2, m2, d2, n[(w+1)%7]);
    decrement_date(&y, &m, &d);
    printf("前の日は西暦%d年%d月%d日%sです。\n", y, m, d, n[(w+6)%7]);
    return 0;
}

みうけい
記事: 8
登録日時: 7年前

Re: たまに曜日が表示されないときがあります。

#8

投稿記事 by みうけい » 7年前

返信が遅くなってすいません。ご回答ありがとうございます。

かずまさんのコードを見たところ、自分の知らない知識がかなり使われていたので、そのことで質問したいと思います。
(もちろんのことですが、自分なりに考えてみました。(新明解c言語入門編を読んでいるところです))

まず、main関数からです。

①35行目のfor文の2つ目の式として使われているputs関数の最後に

 !=EOF

 が付けられているのはputs関数によりループ(for文の)を抜けるのを防ぐためでしょうか?
 (実際に!=EOFを消して、動作させたところ、ループを抜けました。)

②36行目についてです。
 return 1が示しているのは「エラー」ということでしょうか?
 (文字列を入力すると終わるということより)

次は、day_of_week関数からです。

①12行目の式

 y -= m < 3;

 の「m < 3」がよく分かりません。(一番わかりませんでした。)

②13行目の式

 ".\0\3\2\5\0\3\5\1\4\6\2\4"[m](この文は初めて見ました。)

は、

m=1なら0  
m=2なら3
m=3なら2
 …
m=11なら2
m=12なら4

と読んでよろしいでしょうか。(月初めからのズレを修正しているはなんとなく分かります。)

以上です。

かずま

Re: たまに曜日が表示されないときがあります。

#9

投稿記事 by かずま » 7年前

みうけい さんが書きました:①35行目のfor文の2つ目の式として使われているputs関数の最後に

 !=EOF

 が付けられているのはputs関数によりループ(for文の)を抜けるのを防ぐためでしょうか?
そうです。
みうけい さんが書きました:②36行目についてです。
 return 1が示しているのは「エラー」ということでしょうか?
そうです。scanf が失敗したかどうかチェックしなくて、例えば数字以外の文字の
入力があると、無限ループになりますから。
みうけい さんが書きました:①12行目の式

 y -= m < 3;

 の「m < 3」がよく分かりません。(一番わかりませんでした。)
< は演算子です。演算の実行結果は、m が 3 より小さければ 1、
そうでなければ 0 です。
「y -= m < 3;」は式ではなく文です。
この文は、「if (m < 3) y--;」と書き換えることができます。
「if (式) 文」という if文は、式の値が 0 でないとき、文を実行します。
みうけい さんが書きました: ②13行目の式

 ".\0\3\2\5\0\3\5\1\4\6\2\4"[m](この文は初めて見ました。)

は、

m=1なら0  
m=2なら3
m=3なら2
 …
m=11なら2
m=12なら4

と読んでよろしいでしょうか。(月初めからのズレを修正しているはなんとなく分かります。)
文字列は配列ですから、
static const char s[14] = ".\0\3\2\5\0\3\5\1\4\6\2\4"; という宣言のもとで
s[m] を参照するのと同じです。
「月初めからのズレ」というより、
月初めの曜日が、その年の3月1日から何日ずれているかですね。
1月と 2月は、前年の 3月1日からのズレです。

返信

“C言語何でも質問掲示板” へ戻る