/*
 * 日本のカレンダーを実装します。
 * 現行法の祝祭日をサポートします。
 * ・祝日法では、「国民の祝日」と規定されているが、
 *   一般的ではないので、コメントは祝祭日で統一します。
 *
 * このファイルの利用について
 * このファイルの内容は、個人利用・商用利用に関わらず、一部または全部を、
 * 自由に利用していただいてかまいません。
 * 但し、引用元の表示か、著作者が（有）ワイシステムクリエイトであることを
 * 示す、著作権表示を必ず行ってください。
 * また、このファイルの内容を利用した事により、何らかの不利益を被った場合
 * でも、（有）ワイシステムクリエイトは、一切保証できませんので、利用者ご
 * 自身の責任において、ご利用くださるようお願いします。
 *
 * Copyright (C) 2005 Y System Create Corporation. All Rights Reserved.
 *
 * $Date: 2005/03/13 14:41:08 $
 * $Revision: 1.2 $
 *
 * 2007/4 4/29及び5/4の祝日名が変更
 * 2008/5 振替休日の定義を変更
 */

JPCalendar.FIX      = 0;    // 固定日付の祝祭日
JPCalendar.HAPPY    = 1;    // ハッピーマンデー
JPCalendar.EQUINOX  = 2;    // 秋分・春分
JPCalendar.FURIKAE  = 3;    // 振替休日
JPCalendar.KYUUJITU = 4;    // 休日(祝日に挟まれた日)
/*
 * コンストラクタ
 */
function JPCalendar() {

    /*
     * 固定祝祭日テーブルの初期化
     */
    this.fixSyukuSaijitu = new Object();
    var tempFix = [
        [ new JPSyukusaijituInfo("1/1", "元日",
            "New Year's Day", 1948, 9999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("1/15", "成人の日",
            "Coming of Age Day", 1948, 1999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("2/11", "建国記念の日",
            "National Foundation Day", 1966, 9999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("2/24", "昭和天皇の大喪の礼",
            "", 1989, 1989, JPCalendar.KYUUJITU) ],
        [ new JPSyukusaijituInfo("4/10", "皇太子明仁親王の結婚の儀",
            "", 1959, 1959, JPCalendar.KYUUJITU) ],
        [ new JPSyukusaijituInfo("4/29", "天皇誕生日",
            "The Emperor's Birthday", 1926, 1988, JPCalendar.FIX),
          new JPSyukusaijituInfo("4/29", "みどりの日",
            "Greenery Day", 1989, 2006, JPCalendar.FIX) ,
          new JPSyukusaijituInfo("4/29", "昭和の日",
            "Showa Day", 2007, 9999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("5/3", "憲法記念日",
            "Constitution Memorial Day", 1948, 9999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("5/4", "みどりの日",
            "Greenery Day", 2007, 9999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("5/5", "こどもの日",
            "Children's Day", 1948, 9999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("6/9", "皇太子徳仁親王の結婚の儀",
            "", 1993, 1993, JPCalendar.KYUUJITU) ],
        [ new JPSyukusaijituInfo("7/20", "海の日",
            "Marine Day", 1995, 2002, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("9/15", "敬老の日",
            "Respect for the Aged Day", 1966, 2002, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("10/10", "体育の日",
            "Health and Sports Day", 1966, 1999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("11/3", "文化の日",
            "National Culture Day", 1948, 9999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("11/12", "即位礼正殿の儀",
            "", 1990, 1990, JPCalendar.KYUUJITU) ],
        [ new JPSyukusaijituInfo("11/23", "勤労感謝の日",
            "Labor Thanksgiving Day", 1948, 9999, JPCalendar.FIX) ],
        [ new JPSyukusaijituInfo("12/23", "天皇誕生日",
            "The Emperor's Birthday", 1989, 9999, JPCalendar.FIX) ]
    ];
    for (var i = 0; i < tempFix.length; i ++) {
//        AppUtil.printErrorLog(tempFix[i][0].dateInfo);
        this.fixSyukuSaijitu[tempFix[i][0].dateInfo] = tempFix[i];
    }

    /*
     * ハッピーマンデーテーブルの初期化
     * この場合の日付情報は、月と第ｎ月曜日か。
     */
    this.happyMonday = new Object();
    var tempHappy = [
        [ new JPSyukusaijituInfo("1-2", "成人の日",
            "Coming of Age Day", 2000, 9999, JPCalendar.HAPPY) ],
        [ new JPSyukusaijituInfo("7-3", "海の日",
            "Marine Day", 2003, 9999, JPCalendar.HAPPY) ],
        [ new JPSyukusaijituInfo("9-3", "敬老の日",
            "Respect for the Aged Day", 2003, 9999, JPCalendar.HAPPY) ],
        [ new JPSyukusaijituInfo("10-2", "体育の日",
            "Health and Sports Day", 2000, 9999, JPCalendar.HAPPY) ]
    ];
    for (var i = 0; i < tempHappy.length; i ++) {
        this.happyMonday[tempHappy[i][0].dateInfo] = tempHappy[i];
    }
}

/*
 * 指定の年月日に該当する祝祭日を返す。
 * 当該年月日が祝祭日に該当しない場合、nullを返す。
 * 固定の祝日に該当するかを判定する。
 * 上記に該当しなければ、当該月が３月または９月の場合は、
 * 秋分日春分日の判定を行う。
 * 上記に該当しなければ、当該曜日が月曜日の場合、
 * ハッピーマンデーかを判定する。
 * 上記に該当しなければ、当該曜日が月曜日の場合、
 * 振替休日の判定を行う。
 * 上記に該当しなければ、前日が祝祭日かつ翌日が祝祭日かを判定し、
 * 該当する場合、休日とする。
 * 2008/5追加ここから
 * 当日が含まれる週の日曜日が祝日の場合、日曜日から当日の前日までが
 * 全て祝日の場合は、当日が振替休日となる。
 * 2008/5追加ここまで
 * 上記に該当しなければ、祝祭日ではなので、nullを返す。
 */
JPCalendar.prototype.getSyukuSaijitu = function (dateValue, isLimit) {

    var year    = dateValue.getFullYear();
    var month = dateValue.getMonth() + 1;
    var days     = dateValue.getDate();
    var prevDate = null;

    /*
     * 固定の祝祭日を検索する。
     */
    var fixKey = month + "/" + days;
    var fixValues = this.fixSyukuSaijitu[fixKey];

    if (null != fixValues) {

        for (var i = 0; i < fixValues.length; i ++) {

            if (fixValues[i].isReng(year)) {

                return fixValues[i];
            }
        }
    }

    /*
     * 秋分・春分を検索する。
     */
    var equinoxDate = null;
    var jpName = "";
    var enName = "";

    if (3 == month) {

        equinoxDate = JPCalendar.getVernalEquinox(year);
        jpName = "春分の日";
        enName = "";
    }
    if (9 == month) {

        equinoxDate = JPCalendar.getAutumnalEquinox(year);
        jpName = "秋分の日";
        enName = "";
    }

    if (null != equinoxDate) {

        if (dateValue.getFullYear() == equinoxDate.getFullYear() &&
            dateValue.getMonth() == equinoxDate.getMonth()       &&
            dateValue.getDate() == equinoxDate.getDate()) {

            return new JPSyukusaijituInfo(fixKey,
                                          jpName,
                                          enName,
                                          year,
                                          year,
                                          JPCalendar.EQUINOX);
        }
    }

    /*
     * 月曜の場合のみ、チェックする。
     */
    if (1 == dateValue.getDay()) {

        /*
         * ハッピーマンデー
         */
        var happyKey = month + "-" + JPCalendar.getWeekOfMonth(dateValue);
        var happyValues = this.happyMonday[happyKey];

        if (null != happyValues) {

            for (var i = 0; i < happyValues.length; i ++) {

                if (happyValues[i].isReng(year)) {

                    return happyValues[i];
                }
            }
        }

        if (isLimit) {

            return null;
        }

        /*
         * 前日が祝祭日で有れば、振替休日。
         */
        prevDate = JPCalendar.getDateOfDayBefore(dateValue);
        prevDate = this.getSyukuSaijitu(prevDate, true);
        if (null != prevDate) {

            /*
             * 法律上はあり得る。
             * 皇室の行事などで日曜日が休日となったような場合。
             * 現時点ではあり得ない。
             */
            if (JPCalendar.KYUUJITU != prevDate.kubun) {

                var kyujitu = new JPSyukusaijituInfo(fixKey,
                                                     "振替休日",
                                                     "",
                                                     1972,
                                                     2006,  // 2008/5 修正
                                                     JPCalendar.FURIKAE);

                if (kyujitu.isReng(year)) {

                    return kyujitu;
                }
            }
        }
    }

    /*
     * 日曜日以外は、前日と翌日が祝日に当たる場合は、
     * 休日とする。
     */
    else if (0 != dateValue.getDay()) {

        if (isLimit) {

            return null;
        }

        prevDate = JPCalendar.getDateOfDayBefore(dateValue);
        prevDate = this.getSyukuSaijitu(prevDate, true);
        if (null != prevDate) {

            nextDate = JPCalendar.getDateOfNextDay(dateValue);
            nextDate = this.getSyukuSaijitu(nextDate, true);
            if (null != nextDate) {

                if (JPCalendar.KYUUJITU != prevDate.kubun &&
                    JPCalendar.FURIKAE != prevDate.kubun  &&
                    JPCalendar.KYUUJITU != nextDate.kubun &&
                    JPCalendar.FURIKAE != nextDate.kubun) {

                    var kyujitu = new JPSyukusaijituInfo(fixKey,
                                                         "休日",
                                                         "",
                                                         1985,
                                                         9999,
                                                         JPCalendar.KYUUJITU);
                    if (kyujitu.isReng(year)) {

                        return kyujitu;
                    }
                }
            }
        }
    }

    if (isLimit) {

        return null;
    }

    // 2008/5 追加ここから
    /*
     * 平成19年1月1日施行の「国民の祝日に関する法律」改正により
     * 「国民の祝日」が日曜日に当たるときは、その日後においてその日に最も近い
     * 「国民の祝日」でない日を休日とする。
     */
    /*
     * 当該週の日曜日が国民の祝日である場合のみ、チェックを行う。
     */
    var sunday = JPCalendar.getDateDayOfWeek(dateValue, 0);
    sunday = this.getSyukuSaijitu(sunday, true);
    if (null != sunday) {

        /*
         * 月曜日から当日までの全ての日が、祝日かどうかをチェックする。
         */
        var dayOfWeek = dateValue.getDay();
        var count = dayOfWeek - 1;
        for (var i = 0; i < count; i ++) {

            dayOfWeek = JPCalendar.getDateDayOfWeek(dateValue, i + 1);
            dayOfWeek = this.getSyukuSaijitu(dayOfWeek, true);
            if (null == dayOfWeek) {

                /*
                 * 一日でも祝日では無い日があれば終了する。
                 */
                return null;
            }
            if (JPCalendar.FURIKAE == dayOfWeek.kubun) {

                /*
                 * 振替休日は祝日では無いので、終了する。
                 */
                return null;
            }
        }

        /*
         * ループを抜けてきた場合は、
         * 当日までの間が全て祝日であった。
         */
        var kyujitu = new JPSyukusaijituInfo(fixKey,
                                             "振替休日",
                                             "",
                                             2007,
                                             9999,
                                             JPCalendar.FURIKAE);

        if (kyujitu.isReng(year)) {

            return kyujitu;
        }
    }
    // 2008/5 追加ここまで

    return null;
}

/*
 * 春分日・秋分日を求める上での、定数値を初期化
 * このロジックでは、春分点・秋分点が非常にシビアな場合
 * (日付変更時の前後数秒に、当たるような場合)、
 * 正しい日付が取得できない可能性がある。
 */
var gap = 20925216;                         // 一年間のずれ。約５時間４８分
var trueYear = 365 * 24 * 60 * 60 * 1000 + gap;
var baseYear = 2000;
// 春分日の基準 2000年3月20日16時35分
var vernalEquinoxBase = new Date(baseYear, 2, 20, 16, 35);
// 秋分日の基準 2000年9月23日2時28分
var autumnalEquinoxBase = new Date(baseYear, 8, 23, 2, 28);
/*
 * 指定された年の、春分日を求める
 */
JPCalendar.getVernalEquinox = function (year) {

    var vDate = JPCalendar.getEquinox(year, vernalEquinoxBase);
    return vDate;
}

/*
 * 指定された年の、秋分日を求める
 */
JPCalendar.getAutumnalEquinox = function (year) {

    var aDate = JPCalendar.getEquinox(year, autumnalEquinoxBase);
    return aDate;
}

/*
 * 指定された年の春分日・秋分日を求める。
 */
JPCalendar.getEquinox = function (year, baseDate) {

    var difference = year - baseYear;
    var equinox = baseDate.getTime() + difference * trueYear;

    return new Date(equinox);
}

JPCalendar.getWeekOfMonth = function (dateValue) {

    var year    = dateValue.getFullYear();
    var month = dateValue.getMonth() + 1;
    var days    = dateValue.getDate();

    var week = 0;
    if (7 >= days) {
            week = 1;
    }
    else if (14 >= days) {
            week = 2;
    }
    else if (21 >= days) {
            week = 3;
    }
    else if (28 >= days) {
            week = 4;
    }
    else {
            week = 5;
    }
    return week;
}

JPCalendar.dateMillis = 24*60*60*1000; // 一日分の時間(ミリ秒)

/*
 * 指定の日付の前日を取得する。
 */
JPCalendar.getDateOfDayBefore = function (dateValue) {

    return JPCalendar.getDate(dateValue, -1);
}

/*
 * 指定の日付の翌日を取得する。
 */
JPCalendar.getDateOfNextDay = function (dateValue) {

    return JPCalendar.getDate(dateValue, 1);
}

/*
 * 相対日付の取得
 * 引数dateValueからdays日経過した日を返す。
 */
JPCalendar.getDate = function (dateValue, days) {

    var addMillis = JPCalendar.dateMillis * days;
    var curMillis = dateValue.getTime();
    return new Date(curMillis + addMillis);
}

/*
 * 指定の日付が含まれる週の、指定曜日の日付を取得する。
 * dateValueが含まれる週の、dayOfWeekで指定された曜日の日付を返す。
 * 2008/5 追加
 */
JPCalendar.getDateDayOfWeek = function (dateValue, dayOfWeek) {

    /*
     * 指定日付の曜日を取得する。
     * 日曜日が0
     */
    var currentDayOfWeek = dateValue.getDay();
    var diff = dayOfWeek - currentDayOfWeek;
    return JPCalendar.getDate(dateValue, diff);
}

/*
 * 祝祭日情報管理クラス
 * 祝祭日の日付情報
 * 祝祭日の日本語名
 * 祝祭日の英語名
 * 開始年
 * 終了年
 * の、各情報をカプセル化する。
 */
function JPSyukusaijituInfo(dateInfo, jpName, enName, startYear, endYear, kubun) {

    this.dateInfo    = dateInfo;
    this.jpName      = jpName;
    this.enName      = enName;
    this.startYear   = startYear;
    this.endYear     = endYear;
    this.kubun       = kubun;
}

/*
 * 指定の年が、該当範囲かを判定する。
 */
JPSyukusaijituInfo.prototype.isReng = function (year) {

    if (this.startYear <= year && year <= this.endYear) {

        return true;
    }
    return false;
}

JPSyukusaijituInfo.prototype.toString = function () {

    return "dateInfo("+this.dateInfo     +
           ") jpName("+this.jpName       +
           ") enName("+this.enName       +
           ") startYear("+this.startYear +
           ") endYear("+this.endYear     +
           ") kubun("+this.kubun+")";
}
