万年カレンダーを作る前の今月のカレンダーを表示するシンプルなカレンダーを作っていきましょう。
それを土台として万年カレンダーを作ります。
カレンダーと言っても色々な形式があります。
単純に今日の日付を表示するだけの場合もありますし、今月のカレンダーだけを表示したり、一年分のカレンダーを表示することもあります。
また、週と曜日で格子状に組み上げるばかりでなく、一覧にして行毎に並べたり、表示の余白を持たせてメモやイベントを表示することもあります。
今回は最終的に万年カレンダーとして使えるものを作っていきたいと思います。
まずは、今日の日付を表示するものを作り、今月分のカレンダーを作り、フォームと組み合わせて万年カレンダーへと発展させたいと思います。
HTMLファイルの基本的なタグを用意します。
「calendar.html」と名付けて保存してください。
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>カレンダー</title> </head> <body> <h3>カレンダー</h3> <hr> </body> </html>
まず、今日の日付を取得するスクリプトから作っていきましょう。
<html>
<head>
<title>カレンダー</title>
</head>
<body>
<h3>カレンダー</h3>
<hr>
<script type="text/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の中身を表示することができます。
次に曜日を付け足して表示させましょう。
<script type="text/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>
曜日はgetDay()で取得することが出来ます。
日曜日が「0」で、月曜日が「1」、土曜日は「6」になります。
そのままでは、何曜日が判りませんので、表示用の名称を配列変数youbiに入れて準備しておきます。
表示の設定で、「youbi[you]」として、配列からyou番目のデータを取り出しています。
この曜日の仕組みを見ると、getMonthがどうして「0」から月が始まるのか判ります。
英語だと1月は「January」ですから、配列変数で表示を当てなくてはなりません。配列変数は最初が「0」番目なので、1月が「0」の方が都合が良いのです。
このような場合、「+1」は必要ありません。
日曜日に赤、土曜日に青の色を付けてみましょう。
<script type="text/javascript"> //日付と時間の設定 now = new Date(); year = now.getYear(); 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>
もっとも簡単な方法を使いましょう。配列変数youbiの規定値の中に<font>タグも組み込んでしまいます。表示の時に、土曜日だったか日曜日だったかif文で判別させるような、難しいことを考える必要はありません。
もう1つ別の配列変数を利用して、各曜日の色番号を用意するという方法もあります。
youbi_color = new Array("#ff0000","#000000","#000000","#000000","#000000","#000000","#0000ff");
today = year +"年" + mon + "月" + day + "日" + "(<font color='" + youbi_color[you] + "'>" + youbi[you] + "</font>)";
今日の日付を表示するだけなら、関数を使って取り出すだけなので簡単です。
しかし、カレンダーとして表示させたい場合には、閏年などの基礎知識は欠かせません。
また、関数では表示できない内容を取り入れるためには、それらの計算方法も知っておく必要があります。例えば、旧暦、月齢などを表示したい場合です。
【閏年】
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];
【月齢・月相】
月の満ち欠けの周期を月齢という数値で表します。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 - 国民の祝日、国立天文台
今日の日付の下にカレンダーを表示したいと思います。テーブルを作って出力しましょう。
<script type="text/javascript"> //日付と時間の設定 now = new Date(); year = now.getFullYear(); 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>
まず、日付の下に仕切り線を入れます。その下にカレンダーを作っていきましょう。
全ての要素はdocument.write()で直接書き出して行きます。
カレンダーは7つの曜日があるので、1つの行は7つの箱から出来ています。基本形は5つの行から出来ていますので、まずはそこまでを作ってみましょう。
<table>タグ、<tr>タグと<td>タグで、5×7の枠を作ります。日付の代わりに変数mを置いて枠内に数字を表示させています。これは<td>内が空っぽだとテーブルが正常に表示されないため、テーブルの書き出しを確認するための措置です。
ここでは2つのfor文があります。変数nと変数mを使った2つのループです。
1つ目は行を書き出すために<tr>タグを5回繰り返して出力しています。
2つ目は、行の中で<td>タグを7回繰り返し出力して、1つ目のfor文の中にあるので、更に7回繰り返されています。
カレンダーとしては大事なものを足しましょう。枠の上に曜日を表示し、さらにその上に月名を表示しましょう。
<script type="text/javascript"> //日付と時間の設定 now = new Date(); year = now.getFullYear(); 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>
年月を表示する行と曜日を表示する行を追加します。
曜日はfor文を使って7つの曜日を順番に出力しています。
これで随分とカレンダーらしくなりました。
日付の表示が仮のままでしたので、数字が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>"+(n*7+m+1)+"</td>"); } document.write("</tr>"); } document.write("</table>");
変数mも数字が1つずつ増えていますが、行が変わったときにまた0に戻ります。
2行目は数字が7足りません。3行目はまた元に戻るので、14足りません。そこで、行数と7を掛けた数字を変数mに足します。
「m」の部分を「n*7+m」に変更します。
最初が「0」から始まりますが、0の日付はないので1を足します。これで、1から始まるカレンダーになりました。
あとで、if文を使えるようにするため、日付の出力を1行から4行に手順を分けます。
前後のタグの表示と計算式、計算結果の表示に分けました。
//カレンダー表示 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文で分けて処理するための準備です。
カレンダーは全ての枠に日付を出力する訳ではないので、その制御を後で行います。
日曜日から1が始まっていますが、これを正しい位置から始まるようにします。
その月の1日が何曜日かを計算し、そこを「1」の位置とします。
//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(" ");} document.write("</td>"); } document.write("</tr>"); } document.write("</table>");
その月の1日を調べるため、変数fdayを用意します。これは日付型の変数として定義し、今日の年と月と「1」を合わせて、今月の1日の日付に設定します。
次の変数fyouでは、変数fdayから曜日を取り出しています。曜日は数値になっており、日曜が0、月曜が1、と順になって土曜が6です。
では、1日が日曜から始まる場合と水曜から始まる場合を表で見てみましょう。
日 | 月 | 火 | 水 | 木 | 金 | 土 | |
---|---|---|---|---|---|---|---|
日曜始まり | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
水曜始まり | 1 | 2 | 3 | 4 |
始まる位置が3つ移動しています。このとき変数fyouの値も3です。
丁度、変数fyouの値の分だけ変数dの値を小さくすれば、曜日をずらすことになるのです。
このことから、変数dの代入式に「-fyou」を追記します。
次に、if文を使って変数dが0より大きいときだけ日付を表示します。
その次のelseの行で、日付を表示しない場合の空白を出力します。
これでカレンダーの開始位置はうまくできました。
カレンダーの最初の「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 < 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(" ");} document.write("</td>"); } document.write("</tr>"); } document.write("</table>");
1日と違って、最後の日にちは月ごとに違います。
そこで、配列変数ldayで、それぞれの月の日数を既定値として置いておきます。
そして、次に重要なのが閏年です。もし、今年が閏年ならば、2月のデータに「1」を足さなければなりません。また、配列変数では2月のデータは要素の1番に入っていますので注意しましょう。
あとは日付を出力する条件を見直します。if文で「d > 0」という開始位置の条件に加えて、終了位置の条件を加えます。終了位置は、その月の最後の日と同じ日付までなので「d <= lday[mon-1]」とします。
「&&」は2つの並べられた条件を同時に満たすことを意味します。そのため、変数dは「0」より大きく、末日と同じか小さいときに、真となります。
これでようやくカレンダーとして見られる物になったでしょうか。
実はまだ大きな問題点があります。多くのカレンダーは5週間分で5段の表示になっていますが、6段目があるのです。
実際のカレンダーでは5週目のところに2つの日付が小さく表示されることがあります。
//カレンダー表示 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(" ");} document.write("</td>"); } document.write("</tr>"); if (d >= lday[mon-1]) break; } document.write("</table>");
基本の処理を6回に変更し、カレンダーを6段まで作れるようにします。
次にfor文内の最後のところで、日付である変数dが末日以上になったらfor文での処理を終了するようにします。
変数dが最後の日付になった時点でループを抜けるので、日付を超えることはありませんが、以上という意味をしっかり残すために「>=」としています。
これで、5週目の処理が終わった時点で、次の6週目の処理を実行するか、最後の日付を見て判断しています。
「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>)"; (省略)
まず、曜日の選択肢から手を着けましょう。
配列変数youbiの中にある<font>タグを削除します。
次に、配列変数youbi_colorを定義します。7つの値のうち最初と最後だけ色番号を入れます。これが日曜日と土曜日の色となります。空のところは色指定無しです。
表示の設定で、変数todayへの代入文字列の中に<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(" ");} document.write("</td>"); } document.write("</tr>"); if (d >= lday[mon-1]) break; } document.write("</table>");
カレンダー表示の配列変数youbiで曜日の出力と、変数dで日付を出力のところに、<font>タグを追加します。色番号には配列変数youbi_colorを使います。
日付の数字を右寄せにしておきたいので、3つ目のfor文で出力する日付枠の<td>に「align='right'」を追加しておきます。
今日の日付部分に背景を塗って目立たせましょう。
//カレンダー表示 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(" ");} document.write("</td>"); } document.write("</tr>"); if (d >= lday[mon-1]) break; } document.write("</table>");
日付の枠を出力する前に、if文で出力する日付と今日の日付が同じかどうか判断しています。
同じならば、背景に色を着けた枠を出力します。<td>タグにbgcolor属性を指定しましょう。
続けてelseを使い、同じでない場合にこれまで通りの<td>タグを出力させます。
カレンダーの今日の日付に色が付くので、カレンダー上部に表示される日付はない方が良いでしょう。
//画面に表示 //document.write(today); //document.write("<hr>");
画面への出力命令を削除するのではなく、コメントアウトして動作しないようにしておきましょう。行の頭に「//」を付けます。
こうしておけば、動作テストで日付を確認したいときに、書き直す必要がなく、いつでもまた使えます。
どんなプログラムでも動作テストは重要です。様々な条件を試すことで、記述したプログラムが正しいかどうかを調べます。
たまたま今はうまく動いていても、明日うまく動かないこともあります。また、想定外のことも起きるので、できるだけ想定の範囲を広げて対処をしておきます。
カレンダーの場合でも、先月は正しいか、来月は正しいか、年数を変えて閏年もチェックします。
ただ、気長に日付が変わるのを待つわけには行きません。そこで、変数を使ったテストを実施します。
//日付と時間の設定
now = new Date("2008/9/19");
テストは、変数に任意のデータを与えてやることで行います。
例えば、
最初にある変数nowに任意の日付を与えることで、様々な日付のテストができます。
テストは多いほど精度は上がります。また、プログラムを理解していると、テストを無駄に多くすることもなくなるでしょう。
想定外のことを発見するには、まずこれはないだろうという間違った値を入れてみることもあります。上記の例であれば、原則PCのカレンダーの日付が入るので、日付を間違うことはまずないでしょう。
過去にはPC本体の日付が狂ってしまうという問題が発生したことがあります。ただ、これはプログラムのミスという訳ではありません。しかし、それをプログラムでカバーすることは可能でした。
また、JavaScriptでは、年数を取得するのにgetFullYearを使っていますが、以前はgetYearを使っていました。これだとFirefoxでは
テストで間違いがないことが確認できたら、このシンプルなカレンダーは完成です。