[Labyrinthe Noir]>[Top]>[Computer Forum]>[実用JavaScript集]>[こども工作教室]>

万年カレンダーのソースと解説


<html>
<head>
<title>カレンダー</title>
</head>
<body>
<h3>カレンダー</h3>
<hr>

</body>
</html>

【最初の準備】

HTMLファイルの基本的なタグを用意します。
「calendar.html」と名付けて保存してください。

【構想を練る】

カレンダーと言っても色々な形式があります。
単純に今日の日付を表示するだけの場合もありますし、今月のカレンダーだけを表示したり、一年分のカレンダーを表示することもあります。
また、週と曜日で格子状に組み上げるばかりでなく、一覧にして行毎に並べたり、表示の余白を持たせてメモやイベントを表示することもあります。

今回は最終的に万年カレンダーとして使えるものを作っていきたいと思います。
まずは、今日の日付を表示するものを作り、今月分のカレンダーを作り、フォームと組み合わせて万年カレンダーへと発展させたいと思います。


<html>
<head>
<title>カレンダー</title>
</head>
<body>
<h3>カレンダー</h3>
<hr>
<script language="javascript">
//日付の設定
now = new Date();
year = now.getYear();
if (year < 1900) year += 1900;
mon = now.getMonth()+1;
day = now.getDate();

//表示の設定

today = year + "年" + mon + "月" + day + "日";

//画面に表示
document.write(today);
</script>

</body>
</html>

【今日の日付を表示】

日付を取得するために「Date()」関数を使います。
「now = new Date()」とすることで、現在の時間を取得して、変数nowに代入します。
この変数nowには、「 」という形で(時刻を含む)日付型のデータが入っています。

次にその変数nowから年、月、日のデータを個別に取り出します。それぞれ、変数year、変数mon、変数dayに入れておきます。
この時、2つの注意点があります。
getYear()は、ブラウザによって1900年を0として数値を返します。そこで、if文を使って変数yearが1900より小さい場合に、1900を加算するようにしています。
getMonth()は、1月を0として返しますので、月の表示に使うには必ず1を足す必要があります。

getYear()で2008年の場合、IEなら「2008」ですが、Firefoxだと「108」になります。
getMonth()では、1月は「0」、12月は「11」となります。
getDate()には、そのまま日にちが入ります。

if文で単純にyearを判別していますが、万年カレンダーとしては1900年以前のものを表示したいときには問題が生じます。ここでは、現在の日付なので問題はありません。

変数msgには、日付の変数と文字をつなげて、表示用の文字列を作って代入しています。
それを最後の「document.write()」で表示させています。

document.write(today)の「today」を「now」に変更すれば、変数nowの中身を表示することができます。

 


<html>
<head>
<title>カレンダー</title>
</head>
<body>
<h3>カレンダー</h3>
<hr>
<script language="javascript">
//日付と時間の設定
now = new Date();
year = now.getYear();
if (year < 1900) year += 1900;
mon = now.getMonth()+1;
day = now.getDate();
you = now.getDay();

//曜日の選択肢
youbi = new Array("日","月","火","水","木","金","土");

//表示の設定
today = year + "年" + mon + "月" + day + "日" + "(" + youbi[you] + ")";

//画面に表示
document.write(today);
</script>

</body>
</html>

【曜日の表示】

次に曜日を付け足して表示させましょう。

曜日はgetDay()で取得することが出来ます。
日曜日が「0」で、月曜日が「1」、土曜日は「6」になります。
そのままでは、何曜日が判りませんので、表示用の名称を配列変数youbiに入れて準備しておきます。
表示の設定で、「youbi[you]」として、配列からyou番目のデータを取り出しています。

この曜日の仕組みを見ると、getMonthがどうして「0」から月が始まるのか判ります。
英語だと1月は「January」ですから、配列変数で表示を当てなくてはなりません。配列変数は最初が「0」番目なので、1月が「0」の方が都合が良いのです。
このような場合、「+1」は必要ありません。


<html>
<head>
<title>カレンダー</title>
</head>
<body>
<h3>カレンダー</h3>
<hr>
<script language="javascript">
//日付と時間の設定
now = new Date();
year = now.getYear();
if (year < 1900) year += 1900;
mon = now.getMonth()+1;
day = now.getDate();
you = now.getDay();

//曜日の選択肢
youbi = new Array("<font color='#ff0000'></font>","月","火","水","木","金","<font color='#0000ff'></font>");

//表示の設定
today = year + "年" + mon + "月" + day + "日" + "(" + youbi[you] + ")";

//画面に表示
document.write(today);
</script>

</body>
</html>

【曜日に色を付ける】

日曜日に赤、土曜日に青の色を付けて表示します。
これも単純な方法でできます。曜日の選択肢に<font>タグも組み込んでしまいます。

表示の時に、土曜日だったか日曜日だったかif文で判別させるような、難しいことを考える必要はありません。

もちろん、配列変数を利用して、各曜日の色番号を用意しても良いでしょう。

後でデータを利用しやすいように加工しておいたり、逆にデータを別々にして、表示の段階で加工したりします。
データを別々に利用した方が良いのか、一つにした方が良いのかは、全体の流れの中で変化することもあるので、その時々で対処します。

【この時点の calendar.html を別枠で表示】


【カレンダーの基礎知識】

今日の日付を表示するだけなら、関数を使って取り出すだけなので簡単です。
しかし、カレンダーとして表示させたい場合には、閏年などの基礎知識は欠かせません。
また、関数では表示できない内容を取り入れるためには、それらの計算方法も知っておく必要があります。例えば、旧暦、月齢などを表示したい場合です。

【閏年】

1年は365日だが、4年に一度、366日になり2月29日がある。しかし、100年に一度、閏年ではなくなる。ところが、400年に一度、閏年になる。2000年がちょうどこの400年に一度の閏年となった。

これを式で考えると、変数yearが4で割り切れるときは、閏年。しかし、100で割り切れる場合は平年だが、400で割り切れる場合は閏年となる。
割り切れるということは、除算(割り算)で余りが出ないということです。余りがでないということは、「0」ということです。
JavaScriptでは、「/」の代わりに、「%」を使って余り(剰余)を求めます。

year = now.getYear();
if ((year%4 == 0 && year%100 != 0) || (year%400 == 0)) {alert("今年は閏年です");}
else {alert("今年は平年です");}

【曜日】

JavaScriptでは曜日の計算をしなくても関数で簡単に取り出せます。
もし、曜日の関数がない場合は、計算式によって導き出す必要があります。

曜日の算出については、ツェラーの公式を用いる方法が有名です。

参考資料:Wikipedia - ツェラーの公式

【和暦】

yearには西暦年が入っていますが、これを和暦に直して、「明治」「大正」「昭和」「平成」の何年として表示したい場合があります。

明治元年が1868年、大正元年が1912年、昭和元年が1926年、平成元年が1989年です。そこから、単純な引き算で和暦を算出します。平成であれば、西暦から1988を引きます。

year = now.getYear();
if (year > 1867) wareki = year - 1867; //明治
if (year > 1911) wareki = year - 1911; //大正
if (year > 1925) wareki = year - 1925; //昭和
if (year > 1988) wareki = year - 1988; //平成

【旧暦と六曜】

旧暦(太陰太陽暦)に表示される6つの歴注で、先勝、友引、先負、仏滅、大安、赤口があります。
1月1日は必ず先勝で、2月1日は友引、6月1日は赤口で、7月1日はまた先勝となります。1日から順に表記されるので、日付により固定した六曜となっています。
旧暦の月と日を足して6で割った余りが、「0」なら大安、「1」が赤口、「5」が仏滅です。

旧暦との換算式は複雑なのでここでは取り上げません。
以下の変数kyu_monと変数kyu_dayが旧暦の月と日を表しています。

rokuyou = new Array("大安","赤口","先勝","友引","先負","仏滅");
today = rokuyou[(kyu_mon+kyu_day)%6];

参考資料:ながのゆたか氏の旧暦計算JavaScript

【月齢・月相】

月の満ち欠けの周期を月齢という数値で表します。0が新月で約14〜15が満月です。約29.5でまた新月に戻ります。
月の光って見える部分の変化の様子を月相と言います。光る部分が見えないときが新月、全部が光っている状態が満月です。

計算式にはいくつかありますが、用途に応じて精度の良い物を使ったり、精度が悪くても計算が速いものを使う場合もあります。
次のものは比較的簡単なグレゴリオ暦からの算出方法です。

//月齢定数
moonage_g = new Array(0,2,0,2,2,4,5,6,7,8,9,10);
//グレゴリオ歴による計算
moonage = (((year-11)%19)*11+moonage_g[mon-1]+day)%30;

参考資料:wikipedia-月齢福原直人氏のJavaScriptながのゆたか氏の旧暦計算JavaScript

【国民の祝日】

日本の祝日は、国民の祝日に関する法律によって日にちが定められています。
決まった日にちのものと、第何週の月曜日(ハッピーマンデー)とされるものがあります。例えば、元日なら1月1日で固定ですが、体育の日は10月の第2週の月曜日です。
春分の日(4月20日か21日)、秋分の日(9月22日か23日)については、前年2月1日に国立天文台から発表されます。二十四節気の1つとして計算することは可能ですが、実際は観測データを元に決定しています。
祝日が日曜日と重なった場合、祝日を除いて次にくる平日を休日とします。(国民の祝日に関する法律 第3条2)
また、国民の祝日に前後を挟まれた平日は休日となります。(国民の祝日に関する法律 第3条3)

祝日を日にちでデータベース(配列変数)化し、日付をデータベースと照合して判断します。
第2週の場合、8〜14の間の月曜日と考えます。このとき、第2週を表す記号を作るよりも、8〜14までを体育の日としておいて、月曜日は1つしかないので、合致した日だけを算出したほうが良いでしょう。

//祝日(春分の日、秋分の日以外)
syuku_date = new Array("1,1,0","1,8,14","2,11,0","4,29,0","5,3,0","5,4,0","5,5,0","7,15,21","9,15,21","10,8,14","11,3,0","11,23,0","12,23,0");
syuku_name = new Array("元日","成人の日","建国記念の日","昭和の日","憲法記念日","みどりの日","こどもの日","海の日","敬老の日","体育の日","文化の日","勤労感謝の日","天皇誕生日");
for (i=0;i<syuku_date.length;i++) {
sd = syuku_date[i].split(",");
if (sd[2] > 0) {
//ハッピーマンデー
if (mon == sd[0] && you == 1 && day >= sd[1] && day <= sd[2]) syuku = syuku_name[i];
} else {
//日にち固定
if (mon == sd[0] && day == sd[1]) syuku = syuku_name[i];
}
}

document.write("<font color='#ff0000'>"+syuku+"</font>");

参考資料:Wikipedia - 国民の祝日国立天文台


<html>
<head>
<title>カレンダー</title>
</head>
<body>
<h3>カレンダー</h3>
<hr>
<script language="javascript">
//日付と時間の設定
now = new Date();
year = now.getYear();
if (year < 1900) year += 1900;
mon = now.getMonth()+1;
day = now.getDate();
you = now.getDay();

//曜日の選択肢
youbi = new Array("<font color='#ff0000'>日</font>","月","火","水","木","金","<font color='#0000ff'>土</font>");

//表示の設定
today = year + "年" + mon + "月" + day + "日" + "(" + youbi[you] + ")";

//画面に表示
document.write(today);
document.write("<hr>");

//カレンダー表示
document.write("<table border='2'>");
for (n = 0; n < 5; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<td>"+m+"</td>");
}
document.write("</tr>");
}
document.write("</table>");
</script>


</body>
</html>


【区切り線】

今日の日付の下にカレンダーを表示したいと思います。

まず、そこにテーブルを置く前に、区切り線を入れておきます。

【テーブルの作成】

カレンダーは7つの曜日があるので、1つの行は7つの箱から出来ています。
基本形は5つの行から出来ていますので、まずはそこまでを作ってみましょう。

5×7の箱が並びました。変数mを置いて仮に数字を表示させています。

ここには2つのfor文があります。
1つ目は行を書き出すために5回繰り返しています。
2つ目は、その行の中にあって、7回繰り返しますが、1つ目のfor文の中にあるので、7回繰り返すことを5回繰り返しているのです。


<html>
<head>
<title>カレンダー</title>
</head>
<body>
<h3>カレンダー</h3>
<hr>
<script language="javascript">
//日付と時間の設定
now = new Date();
year = now.getYear();
if (year < 1900) year += 1900;
mon = now.getMonth()+1;
day = now.getDate();
you = now.getDay();

//曜日の選択肢
youbi = new Array("<font color='#ff0000'>日</font>","月","火","水","木","金","<font color='#0000ff'>土</font>");

//表示の設定
today = year + "年" + mon + "月" + day + "日" + "(" + youbi[you] + ")";

//画面に表示
document.write(today);
document.write("<hr>");

//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th>"+youbi[m]+"</th>");
}
document.write("</tr>");
for (n = 0; n < 5; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<td>"+m+"</td>");
}
document.write("</tr>");
}
document.write("</table>");
</script>

</body>
</html>


【テーブルヘッダーの追加】

カレンダーとしては大事なものがまだ足りません。
それぞれの枠の上に曜日を示す文字が必要ですし、何月のカレンダーか判った方が良いですね。

年月を表示する行と曜日を表示する行を追加します。
曜日はfor文を使って7つの曜日を順番に出力しています。

これで随分とカレンダーらしくなりました。


(省略)

//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th>"+youbi[m]+"</th>");
}
document.write("</tr>");
for (n = 0; n < 5; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<td>"+(n*7+m+1)+"</td>");
}
document.write("</tr>");
}
document.write("</table>");

(省略)


【日付の表示(1)】

日付の表示が仮のままでしたので、数字が1つずつ増えて行くようにしましょう。

計算式を使ってまずは、順番に数字が増えるようにします。

変数mも数字が1つずつ増えていますが、行が変わったときにまた0に戻ります。
2行目は数字が7足りません。3行目はまた元に戻るので、14足りません。
そこで、行数と7を掛けた数字を変数mに足します。

「m」の部分を「n*7+m」に変更します。

最初が「0」から始まるので「n*7+m+1」と、1を足すことにします。

これで、1から始まるカレンダーになりました。


(省略)

//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th>"+youbi[m]+"</th>");
}
document.write("</tr>");
for (n = 0; n < 5; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<td>");
d = (n*7+m+1);
document.write(d);
document.write(
"</td>");
}
document.write("</tr>");
}
document.write("</table>");

(省略)


【行の分割】

あとで、if文を使えるようにするため、行を分けることにしましょう。
前後のタグの表示と計算式、計算結果の表示に分けました。

こうしておけば、あとでif文を使って、不要な部分は表示しないようにすることができます。


(省略)

//1日
fday = new Date(year+"/"+mon+"/1");
fyou = fday.getDay();

//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th>"+youbi[m]+"</th>");
}
document.write("</tr>");
for (n = 0; n < 5; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<td>");
d = (n*7+m+1-fyou);
if (d > 0) {document.write(d);}
else {document.write("&nbsp;");}

document.write("</td>");
}
document.write("</tr>");
}
document.write("</table>");

(省略)


【日付の表示(2)】

日曜日から1が始まっていますが、これを正しい位置から始めたいと思います。
その月の1日が何曜日か判れば、そこが「1」の場所です。

カレンダーの表示をする前に、今月の1日の曜日を計算します。
新しい日付型の変数fdayを使って、日付を与えます。
次でそこから曜日を取り出しています。

日付からその変数fyouを引くと「1」が正しい位置になります。
「0」やマイナスが出ても気にする必要はありません。
if文を使って、それらを見えなくしています。

if文では、変数dが「0」より大きいときだけ表示をします。「else」を使って、そうでないときは空白を表示させています。

どうですか?これでカレンダーっぽくなったでしょう?
でも、まだまだです。


(省略)

//1日
fday = new Date(year+"/"+mon+"/1");
fyou = fday.getDay();
//末日
lday = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
if ((year%4 == 0 && year%100 != 0) || (year%400 == 0)) {lday[1]++;}

//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th>"+youbi[m]+"</th>");
}
document.write("</tr>");
for (n = 0; n < 5; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<td>");
d = (n*7+m+1-fyou);
if (d > 0 && d <= lday[mon-1]) {document.write(d);}
else {document.write("&nbsp;");}
document.write("</td>");
}
document.write("</tr>");
}
document.write("</table>");

(省略)


【日付の表示(3)】

カレンダーの最初の「1」は正しくできましたが、最後が出来ていません。
今度はその月の日数より多い数字を消さなくてはなりません。

1日と違って、最後の日にちは月ごとに違います。
そこで、それぞれの月の日数をデータとして置いておくことにします。

そして、次に重要なのが閏年です。もし、今年が閏年ならば、2月のデータに「1」を足さないといけません。
また、このとき、2月のデータは「1」番目の配列に入っていますので注意が必要です。

日付の表示のif文を少し改良し、2つ目の条件として変数dが今月の末日以下であることを「&&」で付け加えます。
「&&」は2つの並べられた条件を同時に満たすことを意味します。
そのため、変数dは「0」より大きく、末日と同じか小さいときに、真となるのです。

配列変数ldayの要素は月を表す変数monから1を引いています。これは2月が「1」番目になっているからです。

これでようやくカレンダーとして見られる物になりました。


(省略)

//1日
fday = new Date(year+"/"+mon+"/1");
fyou = fday.getDay();
//末日
lday = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
if ((year%4 == 0 && year%100 != 0) || (year%400 == 0)) {lday[1]++;}
//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th>"+youbi[m]+"</th>");
}
document.write("</tr>");
for (n = 0; n < 6; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<td>");
d = (n*7+m+1-fyou);
if (d > 0 && d <= lday[mon-1]) {document.write(d);}
else {document.write("&nbsp;");}
document.write("</td>");
}
document.write("</tr>");
if (d >= lday[mon-1]) break;
}
document.write("</table>");

(省略)


【日付の表示(4)】

しかし、まだこれで安心はできません。
というのも、基本は5週間としてテーブルを組みましたが、たまに6週目が必要な月があります。
実際のカレンダーでは5週目のところに2つの日付が小さく書かれます。

基本の処理を6回に変更します。
次にfor文内の最後の処理に日付である変数dが末日以上になったらfor文での処理を終了するようにします。

これで、5週目の処理が終わった時点で、次の処理をするかどうか、最後の日にちを見て判断することができました。

「break」はfor文などループ処理を強制的に終了させます。


(省略)

//曜日の選択肢
youbi = new Array("<font color='#ff0000'></font>","月","火","水","木","金","<font color='#0000ff'></font>");
youbi_color = new Array("ff0000","","","","","","0000ff");

//表示の設定
today = year + "年" + mon + "月" + day + "日" + "(<font color='" + youbi_color[you] + "'>" + youbi[you] + "</font>)";

(省略)

//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th><font color='" + youbi_color[m] + "'>" + youbi[m] + "</font></th>");
}
document.write("</tr>");
for (n = 0; n < 6; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<td align='right'>");
d = (n*7+m+1-fyou);
if (d > 0 && d <= lday[mon-1]) {document.write("<font color='" + youbi_color[m] + "'>" + d + "</font>");}
else {document.write("&nbsp;");}
document.write("</td>");
}
document.write("</tr>");
if (d >= lday[mon-1]) break;
}
document.write("</table>");

(省略)

【曜日の色を付けよう】

カレンダーの曜日に色が付いていますが、日付の部分には色が付いていません。
曜日と同じように色を付けたいと思います。

まずは、準備として、曜日の選択肢と今日の日付表示の部分を変更します。

変数youbiの行では、緑の部分を削除します。色番号は次の行に引き継ぎます。

変数youbi_colorに色番号を設定します。

表示の設定には、<font>タグを足します。

カレンダーの表示の曜日と日付の部分にも<font>タグを足します。

【日付の右寄せ】

<td>タグにalignを使って日付の数字を右寄せにしておきましょう。


(省略)

//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th><font color='" + youbi_color[m] + "'>" + youbi[m] + "</font></th>");
}
document.write("</tr>");
for (n = 0; n < 6; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
d = (n*7+m+1-fyou);
if (day == d) {document.write("<td align='right' bordercolor='#ff0000'>");}
else {document.write("<td align='right'>");}

if (d > 0 && d <= lday[mon-1]) {document.write("<font color='" + youbi_color[m] + "'>" + d + "</font>");}
else {document.write("&nbsp;");}
document.write("</td>");
}
document.write("</tr>");
if (d >= lday[mon-1]) break;
}
document.write("</table>");

(省略)

【今日の日付に印を付ける】

ここでは2つの方法を紹介したいと思います。

1つは、今日の日付を赤い枠で囲って表示する方法。もう1つは、背景を赤く色づける方法です。

その他に、<img>タグを使ってアイコン表示する方法もありますが、他の枠との大きさが変わってしまうため、全枠に同じ大きさのアイコンを表示させないとバランスが悪くなってしまいます。

【枠に色を付ける】

テーブルのタグ内に「bordercolor」というオプションを使います。
この方法はIEでは使えますが、Firefoxでは無視されてしまいます。

枠の色を付けたい<td>や<th>というタグにオプションを記述します。

if文で今日の日付(変数day)と処理中の日付(変数d)を比較して、同じならオプション付きのタグを出力します。
そのため、変数dの計算の前にあった<td>タグの出力を後ろに移動しています。


(省略)

//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+year+"年"+mon+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th><font color='" + youbi_color[m] + "'>" + youbi[m] + "</font></th>");
}
document.write("</tr>");
for (n = 0; n < 6; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
d = (n*7+m+1-fyou);
if (day == d) {document.write("<td align='right' bgcolor='#ffaaaa>");}
else {document.write("<td align='right'>");}
if (d > 0 && d <= lday[mon-1]) {document.write("<font color='" + youbi_color[m] + "'>" + d + "</font>");}
else {document.write("&nbsp;");}
document.write("</td>");
}
document.write("</tr>");
if (d >= lday[mon-1]) break;
}
document.write("</table>");

(省略)

【背景に色を付ける】

「bordercolor」の代わりに「bgcolor」を使います。
また、枠の色は濃い赤でしたが、背景色なので淡い色にしておきましょう。


(省略)

//表示の設定
today = year + "年" + mon + "月" + day + "日" + "(<font color='" + youbi_color[you] + "'>" + youbi[you] + "</font>)";

//画面に表示
//document.write(today);
//document.write("<hr>");

//1日
fday = new Date(year+"/"+mon+"/1");
fyou = fday.getDay();

(省略)

【日付の出力をなくす】

カレンダーの中に印が付くので、カレンダー丈夫に表示される日付は消してしまいましょう。
その方がカレンダーとしてはバランスが良いと思います。

画面の表示部分を削除するのではなく、「//」を付けてコメントアウトしておきます。
こうしておけば、動作テストで日付を確認したいときに、書き直す必要がなく、いつでも使えるようにできて便利です。

【この時点の calendar.html を別枠で表示】


【動作テスト】

カレンダーとして完成と言うには、しっかりと動作のテストが必要です。

先月は正しいか、来月は正しいか、今月だけで判断せずに色々な年月で正しく動作することを確認しましょう。

テスト方法は、変数に任意のデータを与えてやることで行います。
例えば、 最初にある変数nowに任意の日付を与えてテスト出来ます。
「now = new Date();」を「now = new Date("2008/9/19")」とすれば、その日付のカレンダーを表示します。

テストで間違いがないことが確認できたら、カレンダーの完成です。


【万年カレンダーを考える】

今月のカレンダーであればこれで完成です。ここからは、万年カレンダーとして使えるように改良を続けて行きます。

万年カレンダーとは、何年の何月か指定して、任意表示ができるカレンダーのことです。
ここまでの行程で、どんな年月のカレンダーでも表示できるように仕組みは完成しています。自動的に今月の年月を選びますが、来月、再来月といつまでもその月のカレンダーを表示することができます。
あとは、その年月を自由に変更することができる仕組みを取り付ければ良いのです。

利用者が自由にデータを入力するにはフォームが必要になります。
フォームを使って、年と月を入力してもらうようにして、それに応じて任意のカレンダーを表示させれば、万年カレンダーとして活用できます。

入力方式にも色々ありますので、それぞれの利点や使いやすさを考えて決めます。
テキスト入力欄を用意して、自由に数字を入力してもらう方法や、セレクトメニューを使って選んでもらう方法、ボタンを押して数字を増減させる方法などが考えられます。

数字を入力してもらう場合、自由度は高まりますが、入力ミスに対してどういう処置をするか考えなければなりません。
ボタンを押して増減させる方法だと、入力ミスを防ぐことができ、カレンダーをめくっていく感覚にも近くなりますが、大きく年月を変更したいときに手間取ります。
選択方式は、見た目に邪魔なものが少なく、メニューを開いたときに大きく飛ぶこともできて、最も便利な気がします。

月については、1〜12までしかないので、選択方式の使い勝手は十分に良いでしょう。
年については、選択肢が多くなると使いにくいので、今年を基準にして前後100年を選択できるようにしようかと思います。


<html>
<head>
<title>万年カレンダー</title>
</head>
<body>
<h3>万年カレンダー</h3>
<hr>
<script language="javascript">
//日付と時間の設定
now = new Date();
year = now.getYear();
if (year < 1900) year += 1900;
mon = now.getMonth()+1;
day = now.getDate();
you = now.getDay();

//曜日の選択肢
youbi = new Array("日","月","火","水","木","金","土");
youbi_color = new Array("ff0000","","","","","","0000ff");

//表示の設定
today = year + "年" + mon + "月" + day + "日" + "(<font color='" + youbi_color[you] + "'>" + youbi[you] + "</font>)";

//画面に表示
//document.write(today);
//document.write("<hr>");

view_cal(year,mon);

function view_cal(cy,cm) {
//1日
fday = new Date(cy+"/"+cm+"/1");
fyou = fday.getDay();
//末日
lday = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
if ((cy%4 == 0 && cy%100 != 0) || (cy%400 == 0)) {lday[1]++;}
//カレンダー表示
document.write("<table border='2'>");
document.write("<tr><th colspan='7'>"+cy+"年"+cm+"月</th></tr>");
document.write("<tr>");
for (m = 0; m < 7; m++){
document.write("<th><font color='" + youbi_color[m] + "'>" + youbi[m] + "</font></th>");
}
document.write("</tr>");
for (n = 0; n < 6; n++) {
document.write("<tr>");
for (m = 0; m < 7; m++){
d = (n*7+m+1-fyou);
if (day == d) {document.write("<td align='right' bgcolor='#ffaaaa'>");}
else {document.write("<td align='right'>");}
if (d > 0 && d <= lday[cm-1]) {document.write("<font color='" + youbi_color[m] + "'>" + d + "</font>");}
else {document.write("&nbsp;");}
document.write("</td>");
}
document.write("</tr>");
if (d >= lday[cm-1]) break;
}
document.write("</table>");
}
</script>

</body>
</html>

【タイトルと関数の変更】

タイトルを「万年カレンダー」に変更します。

カレンダーの表示部分をユーザー関数にして、任意の年と月を変数cy、変数cmで処理できるように改良します。

主処理の最後には、ユーザー関数view_calに今日の年と月を与えて呼び出すように記述します。


<html>
<head>
<title>万年カレンダー</title>
</head>
<body>
<h3>万年カレンダー</h3>
<hr>
<div id="calendar"></div>

<script language="javascript">
//日付と時間の設定
now = new Date();
year = now.getYear();
if (year < 1900) year += 1900;
mon = now.getMonth()+1;
day = now.getDate();
you = now.getDay();

//曜日の選択肢
youbi = new Array("日","月","火","水","木","金","土");
youbi_color = new Array("ff0000","","","","","","0000ff");

//表示の設定
today = year + "年" + mon + "月" + day + "日" + "(<font color='" + youbi_color[you] + "'>" + youbi[you] + "</font>)";

//画面に表示
//document.write(today);
//document.write("<hr>");

view_cal(year,mon);

function view_cal(cy,cm) {
get_cal = "";
//1日
fday = new Date(cy+"/"+cm+"/1");
fyou = fday.getDay();
//末日
lday = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
if ((cy%4 == 0 && cy%100 != 0) || (cy%400 == 0)) {lday[1]++;}
//カレンダー表示
get_cal += "<table border='2'>";
get_cal += "<tr><th colspan='7'>"+cy+"年"+cm+"月</th></tr>";
get_cal += "<tr>";
for (m = 0; m < 7; m++){
get_cal += "<th><font color='" + youbi_color[m] + "'>" + youbi[m] + "</font></th>";
}
get_cal += "</tr>";
for (n = 0; n < 6; n++) {
get_cal += "<tr>";
for (m = 0; m < 7; m++){
d = (n*7+m+1-fyou);
if (day == d) {get_cal += "<td align='right' bgcolor='#ffaaaa'>";}
else {get_cal += "<td align='right'>";}
if (d > 0 && d <= lday[cm-1]) {get_cal += "<font color='" + youbi_color[m] + "'>" + d + "</font>";}
else {get_cal += "&nbsp;";}
get_cal +="</td>";
}
get_cal += "</tr>";
if (d >= lday[cm-1]) break;
}
get_cal += "</table>";


//出力
document.getElementById("calendar").innerHTML = get_cal;

}
</script>

</body>
</html>

【表示位置の変更】

<div>タグを使ってカレンダーの表示場所を用意します。
これによって、ページの再読込をせずにカレンダーを書き換えることができます。

<div>の代わりに<span>を使うことも出来ます。
<div>は、その行を全部使いますが、<span>は文字のように行内に置くことが出来ます。
他のページに組み込んで使う場合に、レイアウトに応じて使い分けると良いでしょう。

ユーザー関数view_calにある、document.writeでカレンダーのパーツを順々に出力していましたが、これを一旦関数get_calに入れてから出力するように変更します。

最初に変数get_calを空にします。
最後に変数get_calを出力しています。

間にあるdocument.writeの文章は全て「get_cal +=」に変更します。
このとき「()」を消すのを忘れないようにしてください。
1つ目を例に、修正前と修正後を比較します。

document.write("<table border='2'>");
get_cal += "<table border='2'>";

表示に関する部分を全て同様に変更します。
13箇所ありますので、「)」の削除も忘れないようにしてください。


<html>
<head>
<title>万年カレンダー</title>
</head>
<body>
<h3>万年カレンダー</h3>
<hr>

<form name="fym" ><select name="syear" >
<script language="javascript">

</script>
</select>年<select name="smon" >
<script language="javascript">

</script>
</select>月</form>

<div id="calendar"></div>

(省略)

【フォームを準備】

カレンダーの上に年月選択用のフォームを作ります。

まずは空の状態で入力フォームを用意しておきます。

年と月の選択肢には<select>タグを使いますが、その中に<option>タグを使って年月の数字を用意します。
数字は単純な並びのものなので、スクリプトを使って作成することにします。
特に、年は前後で200行必要なので、1つ1つタグを書くよりもスクリプトで処理した方が効率が良くなります。今年の年数を基準に変化するので、これもスクリプト処理が必要な理由です。


<html>
<head>
<title>万年カレンダー</title>
</head>
<body>
<h3>万年カレンダー</h3>
<hr>
<script language="javascript">
//設定スクリプト
//日付と時間の設定
now = new Date();
year = now.getYear();
if (year < 1900) year += 1900;
mon = now.getMonth()+1;
day = now.getDate();
you = now.getDay();

//曜日の選択肢
youbi = new Array("日","月","火","水","木","金","土");
youbi_color = new Array("ff0000","","","","","","0000ff");

</script>

<form name="fym" ><select name="syear" >
<script language="javascript">

(省略)

【スクリプトの順番を調節】

これからフォーム内のスクリプトを作るのですが、その中でメインのスクリプトから変数yearや変数monの内容を参照する必要があります。

そこで先に準備としてメインのスクリプトから、最初の変数の設定に関する部分だけをフォームの前に移動します。

青の部分はそのまま移動してくる部分です。スクリプトのタグだけ新しく必要になりますので、コピーしてくると良いでしょう。

今後はこの部分を設定スクリプト、最初に作った部分をメインスクリプト、フォーム内の2つをフォーム内スクリプトと呼ぶことにします。


(省略)

<form name="fym"><select name="syear">
<script language="javascript">
//フォーム内スクリプト
for (n=year-100;n<year+101;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.syear[100].selected = true;

</script>
</select>年<select name="smon">
<script language="javascript">
for (n=1;n<13;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.smon[mon-1].selected = true;

</script>
</select>月</form>

<div id="calendar"></div>

(省略)

【年数の表示】

for文を使って出力を繰り返します。
繰り返しの回数は変数yearの前後100年分です。

変数nを使って、年数を表示させますので、最初の数字を決めます。それが「n=year-100」の部分です。変数yearから100を引くことで100年前から変数nが始まります。
終わりは「year+100」の年です。「n<year+101」は、これより小さい100年目までは処理を実行し、101年目は処理をしないということになります。
for文の「n++」は、1回処理が終わったら変数nに1を足すという意味です。これで-100から順に1づつ増やして+100までの年数が変数nに入れられます。

for文内の処理は、<option>タグを出力するだけです。

for文が終わると、セレクトメニューを今年の表示にします。
<option>タグの中から100番目(最初が0番目)を選択させると今年になります。

【月数の表示】

月数は年数よりも処理がシンプルになっています。

繰り返しは1から始まって12で終わります。
出力内容は年数と同じです。

セレクトメニューは0番目が1月なので、「mon-1」が月数の順番です。


(省略)

<form name="fym"><select name="syear"onChange="change_cal()">
<script language="javascript">
//フォーム内スクリプト
for (n=year-100;n<year+101;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.syear[100].selected = true;
</script>
</select>年<select name="smon" onChange="change_cal()">
<script language="javascript">
for (n=1;n<13;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.smon[mon-1].selected = true;
</script>
</select>月</form>

<div id="calendar"></div>

<script language="javascript">
//メインスクリプト

(省略)

function change_cal() {
cy = document.fym.syear.value;
cm = document.fym.smon.value;
view_cal(cy,cm);

}
</script>

</body>
</html>

【セレクトメニューの修正】

メニューの準備ができたら、次はその操作をしたときの処理を作りましょう。
フォームから年月を切り換えるとカレンダーも切り替わるようにします。

フォームには「onChange」でイベントを追加して、ユーザー関数change_calを呼び出すことにします。
これにより、年月を切り換える度にカレンダーが切り替わります。

【イベント処理】

メインスクリプトの最後にユーザー関数change_calを作ります。

その中では、フォームから年数と月数を取得し、これを先に作ったカレンダー表示のためのユーザー関数view_calへと引き継ぎます。

「fym」がフォーム名、「syear」が年数のセレクトタグ名、「smon」が月数のセレクトタグ名です。


(省略)

function view_cal(cy,cm) {
get_cal = "";
//1日
fday = new Date(cy+"/"+cm+"/1");
fyou = fday.getDay();
//末日
lday = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
if ((cy%4 == 0 && cy%100 != 0) || (cy%400 == 0)) {lday[1]++;}
//カレンダー表示
get_cal += "<table border='2'>";
get_cal += "<tr><th colspan='7'>"+cy+"年"+cm+"月</th></tr>";
get_cal += "<tr>";
for (m = 0; m < 7; m++){
get_cal += "<th><font color='" + youbi_color[m] + "'>" + youbi[m] + "</font></th>";
}
get_cal += "</tr>";
for (n = 0; n < 6; n++) {
get_cal += "<tr>";
for (m = 0; m < 7; m++){
d = (n*7+m+1-fyou);
if ((year == cy) && (mon == cm) && (day == d)) {get_cal += "<td align='right' bgcolor='#ffaaaa'>";}
else {get_cal += "<td align='right'>";}
if (d > 0 && d <= lday[cm-1]) {get_cal += "<font color='" + youbi_color[m] + "'>" + d + "</font>";}
else {get_cal += "&nbsp;";}
get_cal += "</td>";
}
get_cal += "</tr>";
if (d >= lday[cm-1]) break;
}
get_cal += "</table>";

//出力
document.getElementById("calendar").innerHTML = get_cal;
}

(省略)

【今日の日付の修正】

今日の日付の印ですが、「if (day == d)」と日にちだけで印を付ける判断をしていました。
万年カレンダーでは、処理する年月が変わるため、このままでは毎月同じ日に印が付いてしまいます。
そこで、if文の中で年と月も同時に同じかどうか判断する必要があります。

【基本仕様の完成】

これで万年カレンダーとして、自由に年月を選べるようにすることができました。

【この時点の calendar.html を別枠で表示】


<form name="fym">
<input type="button" value="←" onClick="change_month(-1)">
<select name="syear" onChange="change_cal()">
<script language="javascript">
//フォーム内スクリプト
for (n=year-100;n<year+101;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.syear[100].selected = true;
</script>
</select>年<select name="smon" onChange="change_cal()">
<script language="javascript">
for (n=1;n<13;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.smon[mon-1].selected = true;
</script>
</select>月
<input type="button" value="→" onClick="change_month(1)">
</form>

【フォームの改良(1):前月・翌月のボタン】

万年カレンダーとして動作するという意味では完成でしたが、ここからは使いやすいように工夫をして行きたいと思います。

セレクトメニューを使っていくと、少々不便な場合があることに気づきます。
マウスを使って翌月のカレンダーを見たいとき、メニューを開いて、選択してと二度のクリックが必要です。しかも、12月になると、次の1月を見るには年数も変更しなくてはなりません。
これでは12月と翌年1月を比較したいときに操作が不便です。

そこで、フォームの左右に前月と翌月の切換ボタンを取り付けたいと思います。

「←」ボタンを押すと-1ヶ月、「→」のボタンを押すと1ヶ月、年月が進みます。
わざわざ「-1」「1」と表記するのは、今後の応用として自由度を高めるためです。半年後なら「6」、1年前なら「-12」、10年後なら「120」となりますので、全てに対応できるようにユーザー関数を作る予定です。


function change_month(ch) {
cy = document.fym.syear.selectedIndex;
cm = document.fym.smon.selectedIndex;

cy = cy + Math.floor(ch/12);
cm = cm + ch % 12;

document.fym.syear[cy].selected = true;
document.fym.smon[cm].selected = true;
}


【ユーザー関数change_monthの作成(1)】

変数chに送られてくる数値が入ります。これは何ヶ月分カレンダーを進めるかという数値です。
マイナスの場合は戻しますが、今は考えないことにします。

まず、フォームから変数cyと変数cmにセレクトタグの項目を取得しています。
しかし、前回とは違い「value」が「selectedIndex」に変わっています。
これは選択肢の何番目が選ばれているのかを示していて、何という選択肢が選ばれているのか、その内容(value)ではありません。
なぜ、年数と月数を取得しないで、選択番号を取得するのでしょうか。

もちろん、年数を使うことはできるのですが、セレクトメニューを自動的に切り替えるためには、内容ではなく選択番号がないと切り替えることができないからです。
セレクトメニューの変更は「document.form.select.selected = true」という形式でないと指定できないのです。
そのために、まずは年数の選択番号と、月数の選択番号を取得しているのです。

次に、変数cyに対して、「ch/12」を足しています。
これは、12ヶ月で1年なので、割り算をして、更に「Math.floor」を使って小数点以下を切り捨てることで、月数を年数に変換しているのです。
例えば、ch=13ならば、1年と1ヶ月です。「Math.floor(ch/12)」は1になります。1ヶ月の部分は次の行で計算し直すので、ここでは1年が何回分あるのか計算しているのです。

次の4行目が、変数chから月数を取り出している部分です。「%」は余りを計算するので「ch/12」によって発生する小数部分を12で割り切れない残り(余り)として取り出すことができます。
この3行目と4行目で、chに入っていた数値を年数と月数にそれぞれ分けていることになります。

そして、ようやく5行目と6行目で、セレクトメニューを切り替えています。


function change_month(ch) {
cy = document.fym.syear.selectedIndex;
cm = document.fym.smon.selectedIndex;

cy = cy + Math.floor(ch/12);
cm = cm + ch % 12;

if (cm > 11) {cy += 1; cm -= 12;}

document.fym.syear[cy].selected = true;
document.fym.smon[cm].selected = true;

cy = document.fym.syear.value;
view_cal(cy,cm+1);
}

【ユーザー関数change_monthの作成(2)】

しかし、このままでは正しく切換が行われません。

10月の場合、1を足すと11月ですが、12月の場合、1を足すと13月になります。
セレクトメニューに13月はありませんので、エラーが発生します。

そこで、5行目に変数cmを修正するための一文を挿入しましょう。
ここではif文を使って、変数cmが11より大きいときに訂正を行います。12ではなく11になっているのは、変数cmが月数ではなく、選択番号が入っているため、12月は11番目だからです。
修正する内容は、変数cyに1を足し、変数cmから12を引いています。

どうして「cm = 0」ではなく「cm -= 12」なのか。
変数chが「1」ならば常に12月の次は1月です。しかし、変数chがもっと大きな数字になることも想定しているからです。

では、逆に変数chが大きくなり、変数cmが24より大きいときはどうするのか?
いえいえ、変数cmは24以上になることはないのです。
最初にフォームから得られる変数cmは最大で11です。変数chは12で割った余りですからこれも最大11なので、足した「22」が、if文の時点で変数cmに入っている最大の数となるのです。

最後に、ユーザー関数view_calへ変数cyと変数cmを送れば、カレンダーが更新されるのですが、その前に、変数cyを取り直しています。
これはセレクトメニューの切り替えが終わったので、今度は年数が必要になるので、同じ変数cyを使ってセレクトメニューの内容(value)を取ってきています。
変数cmも取ってくれば良いのですが、「+1」をするだけで月数になるので、単純にそうして送っています。

これで変数chが正(プラス)の数の時は正常に動作するものができました。


function change_month(ch) {
cy = document.fym.syear.selectedIndex;
cm = document.fym.smon.selectedIndex;

if (ch > 0) {
cy = cy + Math.floor(ch/12);
cm = cm + ch % 12;
} else {
ch = Math.abs(ch);
cy = cy - Math.floor(ch/12);
cm = cm - ch % 12;
}

if (cm > 11) {cy += 1; cm -= 12;}
if (cm < 0) {cy -= 1; cm += 12;}

document.fym.syear[cy].selected = true;
document.fym.smon[cm].selected = true;

cy = document.fym.syear.value;
view_cal(cy,cm+1);
}

【ユーザー関数change_monthの作成(3)】

次に変数chがマイナスの時の動作を作ります。

最初はこのままで「Math.floor(ch/12)」も計算できると思っていたのですが、どうしても余計な「-1」が出てしまい、前月のボタンが正しく動作しないことが判ります。
両方を1つのプログラムでできない場合は、分岐を作って別々に作ります。計算を駆使してできることもありますが、そこに時間を掛けるよりも、分岐を作った方が手早く作れますし、誰が見ても判りやすくなります。

プログラム作りの上級者になると、どうすればプログラムを短く書くことができるか考えるようになります。
単純に短く書くということは、無駄な部分を削り、プログラムの全体量を減らし、効率よく動くことに繋がります。

そんなときには、数学的な複雑な計算式が使われることがあります。独自の計算式や、特定の表現方法をアルゴリズムと言います。

そこでif文を使って、変数chが正のときと負の時で動作を分けることにします。
if文で変数cyと変数cmへの加算を分岐します。「ch > 0」が正の数、それ以外は負の数として扱っています。「0」は負の数ではありませんが、どちらにしても答えは「0」なのでここでは考えないことにします。

正の数と負の数を分岐するのなら、「ch > 0」は「ch >= 0」とするべきですが、動作上問題がないので、無駄な「=」を省略していると考えてください。

負の数の場合、切り捨てのための「Math.floor」が正しく動作しません。そこで、「Math.abs」を使って変数chの絶対値を使います。
絶対値というのは「-」を取るということです。ただそれだけです。
これで普通に切り捨てをして、正しい年数が取得できます。
式の加算も減算に変わっています。「-」を取ってしまったので、式にちゃんと反映しなくてはいけません。

切り捨ては、小数点以下を切り捨てるだけでなく、捨てると小さい数字に丸まります。
「0.5」ならば「.5」が消えて「0」になります。しかし、「-0.5」の場合、「0」ではなく、「-1」になるのです。
「-0.5」よりも「0」の方が大きく、「-1」の方が小さいからです。

ここでの「-」は、数学的な数を表すためのものではなく、カレンダーの方向を示すためのものですから、「+」でも「-」でも「0」からの距離は同じでなければいけないのです。
そこで絶対値を使って同じ数字に直してから、計算式で「-」方向へ月を移動しているのです。

分岐の後のif文も2つになっています。
今度は「cm < 0」の場合です。前の月ということは、1月の前は12月です。一つ前の年になるので、変数cmから1を引き、月数に12を足しています。


function change_month(ch) {
cy = document.fym.syear.selectedIndex;
cm = document.fym.smon.selectedIndex;

if (ch > 0) {
cy = cy + Math.floor(ch/12);
cm = cm + ch % 12;
} else {
ch = Math.abs(ch);
cy = cy - Math.floor(ch/12);
cm = cm - ch % 12;
}

if (cm > 11) {cy += 1; cm -= 12;}
if (cm < 0) {cy -= 1; cm += 12;}

if (cy < 0) {cy = 0; cm = 0;}
if (cy > 200) {cy = 200; cm = 11;}


document.fym.syear[cy].selected = true;
document.fym.smon[cm].selected = true;

cy = document.fym.syear.value;
view_cal(cy,cm+1);
}



【ユーザー関数change_monthの作成(4)】

動作確認をすると「これで完成!」と思えますが、もう1つ大事なことが残っています。
月数と同様に年数にも上限と下限がありますよね。

0番目の年数より前に行こうとしたら、0番目に戻します。このとき、月数も0番目にしています。

200番目の年数より後ろに行こうとしたら、200番目に戻します。月数も11番目にしています。

【この時点の calendar.html を別枠で表示】


<script language="javascript">
//設定スクリプト

(省略)

//年数の範囲
h_year = 100;

</script>

<form name="fym">
<input type="button" value="←" onClick="change_month(-1)">
<select name="syear" onChange="change_cal()">
<script language="javascript">
//フォーム内スクリプト
for (n=year-h_year;n<year+h_year+1;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.syear[h_year].selected = true;
</script>

(省略)

function change_month(ch) {
cy = document.fym.syear.selectedIndex;
cm = document.fym.smon.selectedIndex;

if (ch > 0) {
cy = cy + Math.floor(ch/12);
cm = cm + ch % 12;
} else {
ch = Math.abs(ch);
cy = cy - Math.floor(ch/12);
cm = cm - ch % 12;
}

if (cm > 11) {cy += 1; cm -= 12;}
if (cm < 0) {cy -= 1; cm += 12;}

if (cy < 0) {cy = 0; cm = 0;}
if (cy > h_year*2) {cy = h_year*2; cm = 11;}

document.fym.syear[cy].selected = true;
document.fym.smon[cm].selected = true;

cy = document.fym.syear.value;
view_cal(cy,cm+1);
}

【年数の範囲を変数で設定する】

年数の範囲を「100」として作ってきましたが、これを変更しようとするとあちこちに散らばった数字を手直ししなくてはなりません。
そこで、設定スクリプトに変数を置いて年数の範囲を設定しておけば、ここを変更するだけで全体に変更の内容が伝わるようにできます。

設定スクリプトの中に変数h_yearを用意します。

次にフォーム内とユーザー関数change_monthの中で、年数の範囲となる部分をこの変数に置き換えます。


<form name="fym">
<table><tr><th>
<select name="syear" onChange="change_cal()">
<script language="javascript">
//フォーム内スクリプト
for (n=year-h_year;n<year+h_year+1;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.syear[h_year].selected = true;
</script>
</select>年<select name="smon" onChange="change_cal()">
<script language="javascript">
for (n=1;n<13;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.smon[mon-1].selected = true;
</script>
</select>月
</th></tr>
<tr><th>

<input type="button" value="←" onClick="change_month(-1)">
<input type="button" value="今月" onClick="change_form(year,mon)">
<input type="button" value="→" onClick="change_month(1)">
</th></tr></table>
</form>

【フォームの改良(2):今月のボタン】

何度も触ってみると、いくつか欲しい機能が出てきます。
まずは、「今月」のカレンダーに表示を戻すボタンを作りたいと思います。

ボタンのレイアウトを変更してから、新しいボタンを追加します。
新しいボタンの左右に前月と翌月のボタンを移動しています。(青字の部分)
新しいボタンをクリックすると、次に作るユーザー関数change_formを通してカレンダーを書き換えています。

ついでに、フォームをテーブルで整えておきます。こうすることで1行目と2行目のフォームが綺麗に中央寄せになります。


function change_form(cy,cm) {
view_cal(cy,cm);
cy = cy - year + h_year;
cm = cm - 1;
document.fym.syear[cy].selected = true;
document.fym.smon[cm].selected = true;
}


【ユーザー関数change_formの作成】

メインスクリプト内にユーザー関数change_formを作ります。

「view_cal(cy,cm)」だけでカレンダーを直接変更することはできるのですが、フォームが今月の表示にならないので、その後にフォームも変更しています。
フォームを変更すると、連動してカレンダーが変更されますが、スクリプトでフォームを変更した場合は「onChange」が実行されないため、カレンダーの変更とフォームの変更の両方を指示する必要があります。

スクリプトによるフォームの変更で「onChange」が動作したほうが便利な気もします。そういうスクリプトもあると思います。
しかし、スクリプトによってイベント処理が発生すると、連鎖的にイベントが発生する危険性があります。場合によってはイベントがループして止まらないという事態も出てしまいます。
そうなると、 イベントの発生箇所や発生のタイミングを調べて回避策をしなくてはいけません。

2行目では、変数cyに入ってきた年数をセレクトメニューの選択番号に変換しています。

3行目も、入ってきた変数cmを選択番号に変換しています。

4行目と5行目でセレクトメニューを変更しています。


<form name="fym">
<table><tr><th>
<select name="syear" onChange="change_cal()">
<script language="javascript">
//フォーム内スクリプト
for (n=year-h_year;n<year+h_year+1;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.syear[h_year].selected = true;
</script>
</select>年<select name="smon" onChange="change_cal()">
<script language="javascript">
for (n=1;n<13;n++) {
document.write("<option value="+n+">"+n);
}
document.fym.smon[mon-1].selected = true;
</script>
</select>月
</th></tr>
<tr><th>
<input type="button" value="≪" onClick="change_month(-12)">
<input type="button" value="←" onClick="change_month(-1)">
<input type="button" value="今月" onClick="change_form(year,mon)">
<input type="button" value="→" onClick="change_month(1)">
<input type="button" value="≫" onClick="change_month(12)">
</th></tr></table>
</form>

【フォームの改良(3):前年と来年のボタン】

前後1年分の移動ができるようにボタンを付けましょう。

1年は12ヶ月なので、それぞれ「-12」「12」をユーザー関数change_monthへ送ります。


(省略)

</th></tr>
<tr><th>
<input type="button" value="≪" onClick="change_month(-12)">
<input type="button" value="←" onClick="change_month(-1)">
<input type="button" value="今月" onClick="change_form(year,mon)">
<input type="button" value="→" onClick="change_month(1)">
<input type="button" value="≫" onClick="change_month(12)">
</th></tr>
<tr><th>

<div id="calendar"></div>
</th></tr></table>
</form>


(省略)

//カレンダー表示
get_cal += "<table border='2'>";
//get_cal += "<tr><th colspan='7'>"+cy+"年"+cm+"月</th></tr>";
get_cal += "<tr>";

(省略)

【テーブルの調節】

フォーム内のテーブルに3行目を作り、その中にカレンダーを入れてしまいましょう。
これでフォームとカレンダーの位置がバランス良くなります。

カレンダーの頭にある年月表示は、フォームに同じ物があるので外しても良いでしょう。
作成中はカレンダーが正しいかどうか判断するために表示を残して置きました。
表示が二重でも悪くはありませんし、ここは自由に判断して残すかどうか決めてください。
表示の出力部分に「//」を付けてコメントアウトしておけば、いつでも元に戻せます。

【この時点の calendar.html を別枠で表示】


【完成】

万年カレンダーが完成しました。
今日の日付の段階では、パッと表示できるので、カレンダーなんて簡単かなと思ったら、実際には色々と手間が掛かります。
ただのカレンダーでも、箱を並べるだけでも注意点がいくつかあり、更にそこにフォームを使って入力に応じたプログラムが必要でした。
どこでも見かける当たり前の物でも、自分で作るとなると手間が掛かるものです。

まだまだ改良の余地はありますので、色々とチャレンジしてみてください。
・土日の背景にも色を付ける。
・祝日、誕生石、誕生花などの情報を表示
・ブラウザをチェックしてbordercolor(IEのみ)とbgcolorを切り替える
・予定をメモする機能を付ける(データベース機能を付ける)
・印刷用のカレンダーを作る(紙のサイズに合わせた大きさで表示する)
・HPに貼り付けるためにHTMLを出力する(変数get_calの内容をテキストで表示させる)
・1年分のカレンダーを同時に表示する


【応用例】

12ヶ月カレンダー…1年分のカレンダーを同時に表示(ブラウザチェック付き)
ロングカレンダー…1月1日〜12月31日までを1つのカレンダーとして表示。月ごとに背景色を変更
六曜カレンダー…旧暦の六曜を表示する万年カレンダー
月齢カレンダー…月齢に応じた月相を表示する万年カレンダー
ワイドカレンダー…横長の1年分カレンダー(12カ国語表示)
月齢ワイドカレンダー…1年分の月齢を表示するカレンダー(6種類表示)


戻る