まめわざの中身その1 Javascriptでカレンダーを作る

まめわざは、なるべく自社で開発するという方針から、細部まで自社で作成しております。
以前から企画しておりましたが、今後「まめわざの中身」シリーズとして、構成部品の内容を紹介してまいります。
自社開発のために非効率な部分もあるかと思いますが、少しでも役に立つ部分があれば幸いです。

今回は、その1としてJavascriptでカレンダーを描写する方法をご紹介いたします。

コード

var dfn_holidays = {"2015/1/1":1};
var dfn_thead = ["日", "月", "火", "水", "木", "金", "土"];

var cal = function(year, month) {
    //現在のタイムスタンプ
    var now_ts = new Date().getTime();

    //表示対象月の1日の曜日
    var first_day = new Date(year, month - 1, 1).getDay();
    var last_date = new Date(year, month, 0).getDate();

    //thead
    var thead = "<tr>";
    for(var day = 0; day <= 6; day++) {
        thead += "<td" + (day == 0 || day == 6 ? " class=\"" + (day == 0 ? "sun" : "sat") + "\"" : "") + ">" + dfn_thead[day] + "</td>";
    }
    thead += "</tr>";

    //tbodyを生成
    var tbody = "";
    for(var row = 1, date = 0, fin = false; row <= 6; row++) {
        if(fin) {
            break;
        }

        tbody += "<tr>";
        for(var day = 0; day <= 6; day++) {
            var td = "";
            var cls = [];

            //その月の最初の日に達したらdateをカウント開始
            if(date == 0 && day == first_day) {
                date++;
            }

            //dateが存在する場合
            if(date >= 1 && date <= last_date) {
                td = date;

                //クラスの設定:holiday
                if(dfn_holidays[year + "/" + month + date]) {
                    cls.push("holiday");
                }

                //クラスの設定:past(過去の場合)
                if(new Date(year, month - 1, date, 23, 59, 59).getTime() < now_ts) {
                    cls.push("past");
                }

                date++;
            }

            //dateが上限を超えた場合
            if(date > last_date) {
                fin = true;
            }

            //クラスの設定:sat(土曜日)・sun(日曜日)
            if(day == 0) {
                cls.push("sun");
            } else if(day == 6) {
                cls.push("sat");
            }

            tbody += "<td" + (cls.length > 0 ? " class=\"" + cls.join(" ") + "\"" : "") + ">" + td + "</td>";
        }
        tbody += "</tr>";
    }

    return "<table><caption>" + year + "/" + month + "</caption><thead>" + thead + "</thead><tbody>" + tbody + "</tbody></table>";
}; 

関数の利用方法

あるyear(4桁)とmonthに応じたカレンダーを生成する関数を定義します。
var cal = function(year, month) {};
戻り値はテーブルのHTMLですので、以下の利用例のようにご利用いただけます(利用例では<div id="test"></div>にカレンダーを設置しています)。 

  • 非jQuery
    document.getElementById("test").innerHTML = cal(2015, 8);
  • jQuery
    $("#test").html(cal(2015, 8) );

中身の解説

処理の骨格

下記のように「6行のループの中で、日~土のループを実行してる」のが、この処理の骨格です。
6列のループは、カレンダーが最大6行にまでしかならない(6x7=42マスあれば、どのような月でも描写できる)ためです。

//↓6行のループを実行
for(var row = 1; row <= 6; row++) {
     //曜日のループ(0:日~6:土)を実行
     for(var day = 0; day <= 6; day++) {
     }
} 

日付の表示

次に、この骨格に日付表示を設置します。
まず、「いつから日付のカウントをはじめるか」の判定のために、対象月の最初の日(○月1日)の曜日を下記のように用意します。
var first_day = new Date(year, month - 1, 1).getDay();
new Date(年, 月, 日)としてDateオブジェクトを定義する際、関数の引数であるyearとmonth(ただしJavascriptのmonthは表示する月マイナス1になるのに注意)に合わせて「日=1」を指定し、getDay()で曜日番号を取得します。

同じく、「いつになったらカウントをやめるか」の判定のために、対象月の最後の日付を下記のように取得します。
var last_date = new Date(year, month, 0).getDate();
月に翌月(month=month - 1 + 1)、日に0を指定し、「次月の0日」を指定することで「当月の最終日」をgetDate()から取得します。

これらを使ってループを以下のようにします。

for(var row = 1, date = 0; row <= 6; row++) {
    tbody += "<tr>";
     for(var day = 0; day <= 6; day++) {
        var td = "";

         //その月の最初の日に達したらdateをカウント開始
        if(date == 0 && day == first_day) {
            date++;
        }

        if(date >= 1 && date <= last_date) {
            td = date;
            date++;
        }

        tbody += "<td>" + td + "</td>";
    }
    tbody += "</tr>";
}
 

dateは0からスタートし、初めにdayがfirst_dayになる日にdateのカウントをスタートします。
以降、last_dateに達するまでdateのカウントは続き、tdタグ内にdateを設置します。
尚、カレンダーは常に6行とは限らず4~5行の月もあるため、「date > last_date」となった時点で外側のrowのループを抜け出す処理を加えることでこれに対応できます。上のコードではfinというフラグを用いてこれを実行しています。

クラスの設置

その他の部分は、全てクラスの設置です。下記のように3種類のクラスを別々に判定して設置しています。

  • 土日(sat/sun)
    土曜(day=6)の際にsatクラス、日曜(day=0)の際にsunクラスをそれぞれ設置しています
  • 祝日(holiday)
    dfn_holidaysに一致する日付には国民の祝日としてholidayクラスを設置します。dfn_holidaysはYYYY/MM/DD型で自由に増減できます
  • 過去の日付(past)
    タイプスタンプの比較で過去の日付の場合はpastクラスを設置しています

テーブルヘッダーの設置

最後に、下記のように曜日をループしてテーブルのヘッダを作成します。この際、0(日曜)~6(土曜)の変換用にdfn_theadを利用します。
必要に応じて、土曜・日曜にクラスを設定します。
ヘッダーが不要な場合は丸ごとカットして構いません。

//thead
var thead = "<tr>";
for(var day = 0; day <= 6; day++) {
    thead += "<td" + (day == 0 || day == 6 ? " class=\"" + (day == 0 ? "sun" : "sat") + "\"" : "") + ">" + dfn_thead[day] + "</td>";
}
thead += "</tr>"; 

2015/8/11