GCalendar Holidays - jui Datepicker 専用版

[GCallendar Holidays](http://0-oo.net/sbox/javascript/google-calendar-holidays) は、jQuery UI Datepicker に対応した非常に便利な javascript のユーティリティーで、Googleカレンダーから JSONP で日本の祝日一覧を取得してくれるものです。

## jQuery UI Datepicker に GCalendar Holidays を使う

例えば、素の DatePicker ではこのような表示になります。

![datepicker-def.png](https://qiita-image-store.s3.amazonaws.com/0/12310/f01e9e3b-a3f3-202a-9237-318966f30ae1.png)

これが、GCalendar Holidays を使うだけで、次のように、日本の祝日・休日を表示してくれます。ついでに、土曜日・日曜日も色分けしてくれるので、カレンダーが非常に見易くなって、至れり尽くせりです。

![datepicker-gch.png](https://qiita-image-store.s3.amazonaws.com/0/12310/c4b1dd44-7c5a-ac6e-892e-d635b577c735.png)

知らない人のために、念のために言うと、五月五日の上にカーソルをホバーすると、「こどもの日」であることをポップアップ表示で知らせるということまでやってくれます。

## Google Calendar API v3 への対応 - サーバ側プログラムが必要

Google Calendar API が v2 から v3 にアップグレードされ、v2 が廃止されたことに対応して、[GCallendar Holidays](http://0-oo.net/sbox/javascript/google-calendar-holidays) は、サーバ側でのプログラムを必要とするものに変更されました。

これは、主として、v3 から必要になった **API キー** を javascript で公開するのはよろしくないだろう、サーバ側プログラムの中に隠さなければなるまい、という判断によるものと推測します。

しかし、サーバ側プログラムが必要になったことで、GCalendar Holidays の美点であった **手軽さ** が失われたことは事実です。

## 割り切って、jui Datepicker 専用に改造した

元来、GCalendar Holidays は、日本の祝日を取得するだけでなく、Google Calendar のさまざまなカレンダー・データを取得することが出来る拡張性を目指して作られていました。また、jQuery UI Datepicker 専用でもありませんでした。

しかし、少なくとも私は、GCalendar Holidays を jQuery UI Datepicker との組み合わせ以外で使おうとは思いません。

そこで、割り切って、jQuery UI Datepicker 専用の、手軽に使える GCalendar Holidays に改造しました。**サーバ側プログラムは不要**です。ただし、**API キーはさらけ出し** にしなければなりません。

以下にそのソースを公開します。これで良ければ、自由にお使いください。

## jui-datepicker-jp-holidays.js

```js
/**
 *  GCalendar Holidays - Googleカレンダーから日本の祝日を取得
 *  @see       http://0-oo.net/sbox/javascript/google-calendar-holidays
 *  @version   0.6.0
 *  @copyright 2008-2014 dgbadmin@gmail.com
 *  @license   http://0-oo.net/pryn/MIT_license.txt (The MIT license)
 */
var GCalHolidays = {
    /** Google Calendarから情報を取得するAPIのURL */
    apiUrl : "https://www.googleapis.com/calendar/v3/calendars/",
    /** 「日本の休日」カレンダー */
    calendarId: "japanese__ja@holiday.calendar.google.com",
    /** API key ... あなたがアクセス出来る API キーを設定すること */
    apiKey: "PUT_YOUR_GOOGLE_API_KEY_HERE",
    /** jQuery UI Datepicker用のstyle（Themeに合わせてお好みで） */
    datepickerStyles: {
        sunday:   "color: #f00", //日曜日
        saturday: "color: #0af", //土曜日
        holiday:  "color: #f00"  //祝日
    }
};

/**
 *  祝日を取得する
 *  @param  Function    callback    データ取得時に呼び出されるfunction
 *  @param  Number      year        年
 */
GCalHolidays.get = function(callback, year, month) {
    //日付範囲
    var start = [year, "01", "01"].join("-");
    var end = [year, "12", "31"].join("-");

    this._caches = (this._caches || {});
    this._userCallback = callback;
    this._month = month;

    var cache = this._caches[year];

    if (cache) {    //取得済みの場合はそれを使う
        callback(cache, month);
        return;
    }

    //URL作成
    var url = GCalHolidays.apiUrl +
            encodeURIComponent(GCalHolidays.calendarId) + "/events" +
            "?key=" + GCalHolidays.apiKey +
            "&timeMin=" + start + "T00:00:00.000Z" +
            "&timeMax=" + end + "T23:59:59.000Z" +
            "&fields=items(start,summary)" +
            "&callback=GCalHolidays.decode";
    // timeMin, timeMax は、この形式でないと、何故か bad request になる
    // http://stackoverflow.com/questions/17133777/google-calendar-api-400-error

    //scriptタグ生成
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = url;
    script.charset = "UTF-8";
    document.body.appendChild(script);
};

/**
 *  JSONPによりGoogle Calendar API経由で呼び出されるfunction
 *  @param  Object  gdata   カレンダーデータ
 */
GCalHolidays.decode = function(gdata) {
    var days = GCalHolidays._entries2days(gdata.items);
    if (days.length > 0) { // 空っぽのときもあるぞ
        var year = days[0].year;
        //キャッシュする
        this._caches[year] = days;
        //コールバック
        this._userCallback(days, this._month);
    }
};

/**
 *  JSONPで取得したデータから日付情報を取り出す
 *  @param  Array   entries スケジュール
 *  @return Array   日付情報（year, month, date, title）
 */
GCalHolidays._entries2days = function(entries) {
    var days = [];
    var cnt = 0;

    if (!entries) {
        return days;
    }

    //日付順にソート
    entries.sort(function(a, b) {
        return (a.start.date > b.start.date) ? 1 : -1;
    });

    //シンプルな器に移す
    for (var i = 0, len = entries.length; i < len; i++) {
        var entry = entries[i];
        var ymd = entry.start.date.split("T")[0].split("-");

        days[cnt] = { //年月日は使いやすいように数値にする
            year: ymd[0] * 1,
            month: ymd[1] * 1,
            date: ymd[2] * 1,
            title: entry.summary
        };
        cnt++;
    }
    return days;
};

/**
 *  jQuery UI Datepickerのカレンダーに祝日を表示する
 *  @param  Number  year    表示する年
 *  @param  Number  month   表示する月
 *  @param  Object  inst    Datepicker
 *  @see http://jqueryui.com/demos/datepicker/
 */
GCalHolidays.datepicker = function(year, month, inst) {
    setTimeout(function() { //処理後に対象のdivが再構築されるケースを回避
        for (var i = 0, len = (inst.settings.numberOfMonths || 1); i < len; i++) {
            GCalHolidays.get(function(holidays, mon) {
                for (var j = 0, len2 = holidays.length; j < len2; j++) {
                    var h = holidays[j];
                    if (h.month > mon + 1) {
                        break; // 目的の月（とその次の月）が既に過ぎた
                    }
                    if (h.month >= mon - 1) {
                        var s = "[data-year=" + h.year + "][data-month=" + (h.month - 1) + "] a";
                        inst.dpDiv.find(s).each(function() {
                            if ($(this).text() == h.date) {
                                $(this).addClass("gcal-holiday").attr("title", h.title);
                                return false;
                            }
                        });
                    }
                    // 前後の月の祝日にも対応 ... ただし、1月と12月は、年が変るので、対応できない
                }
            }, year, month);
            
            month++;
            
            if (month > 12) {
                year++;
                month = 1;
            }
        }
    }, 1);
};

/**
 *  jQuery UI Datepickerが有効な場合はイベントハンドラとstyleをセットする
 */
if (window.$ && $.datepicker && $.datepicker.setDefaults) {
    $.datepicker.setDefaults({
        beforeShow: function(input, inst) {
            var date = $(input).datepicker("getDate") || new Date();
            GCalHolidays.datepicker(date.getFullYear(), date.getMonth() + 1, inst);
        },
        beforeShowDay: function(date) { //土日のclass属性
            return [true, { 0: "gcal-sunday", 6: "gcal-saturday" }[date.getDay()] || ""];
        },
        onChangeMonthYear: GCalHolidays.datepicker
    });

    $(function() {  //ページ表示後に土日・祝日用のstyleをセット
        var styles = GCalHolidays.datepickerStyles;
        var css = "";
        css += ".gcal-sunday   .ui-state-default { " + styles.sunday + " } ";
        css += ".gcal-saturday .ui-state-default { " + styles.saturday + " } ";
        css += ".ui-widget-content .gcal-holiday { " + styles.holiday + " }";
        $("head").append($('<style type="text/css">' + css + "</style>"));
    });
}
```
### API キー

上記のコードで、一個所だけ、修正が必要なところがあります。
`apiKey: "PUT_YOUR_GOOGLE_API_KEY_HERE"` の所だけは、あなたが利用できる (おそらくは、あなた自身が取得した) **API キー** に置き換えなければなりません。

Google Calendar (あるいは、一般に Google のサービス) の API キーを取得する方法については、ウェブ上にいくらでも情報があります。例えば、[Google API v3 を利用しよう！〜APIキーの取得方法〜](http://hamatte-wataru.com/google_api_key/) などを参照してください。

「許可対象リファラー」を適切に設定すれば、API へのアクセスをあなたが管理するサイトからのみに限定することが出来ます。