/*
 * DateUtil.java
 * 
 * Author: http://www.hrtdotnet.jp/
 * 
 */
package jp.hrtdotnet.java.util;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

import jp.hrtdotnet.java.lang.DateFormatException;

/**
 * 日付関連の機能のユーティリティクラスです。
 * このクラスは2006年時点でのグレゴリオ暦を元に作成されています。
 * 動作の確認はLocale.JAPANで確認しています。
 * 
 * @version 2.0
 * @author hrtdotnet.jp
 * <br>http://www.hrtdotnet.jp/
 * @since 2.0
 */
public class DateUtil {

    /** YYYYMMDD 形式のフォーマットを示す値です。 */
    public static final String FORMAT_YYYYMMDD = "yyyyMMdd";
    
    /** YYYYMM 形式のフォーマットを示す値です。 */
    public final static String FORMAT_YYYYMM = "yyyyMM";
    
    /** YYMMDD 形式のフォーマットを示す値です。 */
    public final static String FORMAT_YYMMDD = "yyMMdd";
    
    /** YYMM 形式のフォーマットを示す値です。 */
    public final static String FORMAT_YYMM = "yyMM";
    
    /** MMDD 形式のフォーマットを示す値です。 */
    public final static String FORMAT_MMDD = "MMdd";
    
    /** YYYY 形式のフォーマットを示す値です。 */
    public final static String FORMAT_YYYY = "yyyy";
    
    /** YY 形式のフォーマットを示す値です。 */
    public final static String FORMAT_YY = "yy";
    
    /** MM 形式のフォーマットを示す値です。 */
    public final static String FORMAT_MM = "MM";
    
    /** DD 形式のフォーマットを示す値です。 */
    public final static String FORMAT_DD = "dd";

    /** 1月の日数 */
    public static final int JANUARY_DAYS = 31;
    /** 2月の日数 */
    public static final int FEBRUARY_DAYS = 29;
    /** 2月の日数（通常年） */
    public static final int FEBRUARY_NORMAL_DAYS = 28;
    /** 2月の日数（うるう年） */
    public static final int FEBRUARY_LEAP_DAYS = 29;
    /** 3月の日数 */
    public static final int MARCH_DAYS = 31;
    /** 4月の日数 */
    public static final int APRIL_DAYS = 30;
    /** 5月の日数 */
    public static final int MAY_DAYS = 31;
    /** 6月の日数 */
    public static final int JUNE_DAYS = 30;
    /** 7月の日数 */
    public static final int JULY_DAYS = 31;
    /** 8月の日数 */
    public static final int AUGUST_DAYS = 31;
    /** 9月の日数 */
    public static final int SEPTEMBER_DAYS = 30;
    /** 10月の日数 */
    public static final int OCTOBER_DAYS = 31;
    /** 11月の日数 */
    public static final int NOVEMBER_DAYS = 30;
    /** 12月の日数 */
    public static final int DECEMBER_DAYS = 31;

    /** 1月 */
    public static final int JANUARY = 1;
    /** 2月（共通） */
    public static final int FEBRUARY = 2;
    /** 2月（うるう年） */
    public static final int FEBRUARY_LEAP = 13;
    /** 2月（通常年） */
    public static final int FEBRUARY_NORMAL = 14;
    /** 3月 */
    public static final int MARCH = 3;
    /** 4月 */
    public static final int APRIL = 4;
    /** 5月 */
    public static final int MAY = 5;
    /** 6月 */
    public static final int JUNE = 6;
    /** 7月 */
    public static final int JULY = 7;
    /** 8月 */
    public static final int AUGUST = 8;
    /** 9月 */
    public static final int SEPTEMBER = 9;
    /** 10月 */
    public static final int OCTOBER = 10;
    /** 11月 */
    public static final int NOVEMBER = 11;
    /** 12月 */
    public static final int DECEMBER = 12;
    /** 未定義月 */
    public static final int UNDEFINED_MONTH = 0;
    /** 月の最小値 */
    public final static int MIN_MONTH_OF_YEAR = 1;
    /** 月の最大値 */
    public final static int MAX_MONTH_OF_YEAR = 12;
    /** 日の最小値 */
    public final static int MIN_DAY_OF_MONTH = 1;
    /** 日の最大値 */
    public final static int MAX_DAY_OF_MONTH = 31;

    /** 検証：正常 */
    public final static int VALIDATE_NORMAL = 0;
    /** 検証：月の範囲不正 */
    public final static int VALIDATE_MONTH_OVERRANGE = 1;
    /** 検証：日の範囲不正 */
    public final static int VALIDATE_DAY_OVERRANGE = 1;

    /**
     * 昨日のデータを持ったCalendarを取得します。
     * 
     * @return 昨日のCalendarオブジェクト
     * @since 2.0
     */
    public static Calendar getYesterday() {
        return getYesterday(null);
    }

    /**
     * 指定したCalendarの一日前のデータを持ったCalendarを取得します。
     * 取得するCalendarオブジェクトは指定したオブジェクトとは別のものです。
     * 
     * @param c Calendarオブジェクト。
     *           nullの場合は実行時にCalendarが生成されて利用する
     * @return 一日前のCalendarオブジェクト
     * @since 2.0
     */
    public static Calendar getYesterday(Calendar c) {        
        if (c == null) {
            c = Calendar.getInstance();
        } else {
            c = (Calendar)c.clone();
        }
        int y = c.get(Calendar.YEAR);
        int m = c.get(Calendar.MONTH);
        int d = c.get(Calendar.DAY_OF_MONTH);
        if (d == c.getActualMinimum(Calendar.DAY_OF_MONTH) && m == Calendar.JANUARY ) {
            c.set(Calendar.YEAR, y - 1);
            c.set(Calendar.MONTH, Calendar.DECEMBER);
            c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));
        } else if (d == c.getActualMinimum(Calendar.DAY_OF_MONTH)) {
            c.set(Calendar.MONTH, m - 1);
            c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));
        } else {
            c.set(Calendar.DAY_OF_MONTH, d - 1);            
        }
        return c;
    }

    /**
     * 翌日のデータを持ったCalendarを取得します。
     * 取得するCalendarオブジェクトは指定したオブジェクトとは別のものです。
     * 
     * @return 翌日のCalendarオブジェクト
     * @since 2.0
     */
    public static Calendar getTomorrow() {
        return getTomorrow(null);
    }

    /**
     * 指定したCalendarの翌日のデータを持ったCalendarを取得します。
     * 取得するCalendarオブジェクトは指定したオブジェクトとは別のものです。
     * 
     * @param c Calendar オブジェクト。
     *           nullの場合は実行時にCalendarが生成されて利用する
     * @return 翌日のCalendarオブジェクト
     * @since 2.0
     */
    public static Calendar getTomorrow(Calendar c) {        
        if (c == null) {
            c = Calendar.getInstance();
        } else {
            c = (Calendar)c.clone();
        }
        int y = c.get(Calendar.YEAR);
        int m = c.get(Calendar.MONTH);
        int d = c.get(Calendar.DAY_OF_MONTH);
        if (d == c.getActualMaximum(Calendar.DAY_OF_MONTH) && m == Calendar.DECEMBER ) {
            c.set(Calendar.YEAR, y + 1);
            c.set(Calendar.MONTH, Calendar.JANUARY);
            c.set(Calendar.DAY_OF_MONTH, c.getActualMinimum(Calendar.DAY_OF_MONTH));
        } else if (d == c.getActualMaximum(Calendar.DAY_OF_MONTH)) {
            c.set(Calendar.MONTH, m + 1);
            c.set(Calendar.DAY_OF_MONTH, c.getActualMinimum(Calendar.DAY_OF_MONTH));
        } else {
            c.set(Calendar.DAY_OF_MONTH, d + 1);
        }
        return c;
    }
    
    /**
     * 指定したCalendarの日付文字列を取得します。
     * 
     * @param format 日付文字列の形式
     * @param c 対象のCalendarオブジェクト
     * @return 日付文字列
     * @since 2.0
     */
    public static String getString(Calendar c, String format) {        
        if (c == null) {
            c = Calendar.getInstance();
        } else {
            c = (Calendar)c.clone();
        }
        DateFormat df = new SimpleDateFormat(format);
        return df.format(c.getTime());
    }

    /**
     * 指定したCalendarの日付文字列を取得します。
     * 取得する日付文字列の形式はYYYYMMDDです。
     * 
     * @param c 対象の Calendar オブジェクト
     * @return 日付文字列
     * @since 2.0
     */
    public static String getString(Calendar c) {
        return getString(c, FORMAT_YYYYMMDD);
    }

    /**
     * 昨日の日付文字列を指定した形式で取得します。
     * 
     * @param format 日付文字列の形式
     * @return 日付文字列
     * @since 2.0
     */
    public static String getYesetdayString(String format) {
        Calendar c = getYesterday();
        return getString(c, format);
    }

    /**
     * 昨日の日付文字列を取得します。
     * 取得する日付文字列の形式はYYYYMMDDです。
     * 
     * @return 日付文字列
     * @since 2.0
     */
    public static String getYesetdayString() {
        return getYesetdayString(FORMAT_YYYYMMDD);
    }
    
    /**
     * 明日の日付文字列を指定した形式で取得します。
     * 
     * @param format 日付文字列の形式
     * @return 日付文字列
     * @since 2.0
     */
    public static String getTomorrowString(String format) {
        Calendar c = getTomorrow();
        return getString(c, format);
    }

    /**
     * 明日の日付文字列を取得します。
     * 取得する日付文字列の形式はYYYYMMDDです。
     * 
     * @return 日付文字列
     * @since 2.0
     */
    public static String getTomorrowString() {
        return getTomorrowString(FORMAT_YYYYMMDD);
    }
    
    
    /**
     * 今日の日付文字列を指定した形式で取得します。
     * 
     * @param format 日付文字列の形式
     * @return 日付文字列
     * @since 2.0
     */
    public static String getTodayString(String format) {
        return getString(Calendar.getInstance(), format);
    }

    /**
     * 今日の日付文字列を取得します。
     * 取得する日付文字列の形式はYYYYMMDDです。
     * 
     * @return 日付文字列
     * @since 2.0
     */
    public static String getTodayString() {
        return getTodayString(FORMAT_YYYYMMDD);
    }

    /**
     * 指定したCalendarの先月のデータを持ったCalendarを取得します。
     * 取得するCalendarオブジェクトは指定したオブジェクトとは別のものです。
     * 
     * @param c Calendarオブジェクト。
     *           nullの場合は実行時にCalendarが生成されて利用する
     * @return 先月のCalendarオブジェクト
     * @since 2.0
     */
    public static Calendar getLastMonth(Calendar c) {        
        if (c == null) {
            c = Calendar.getInstance();
        } else {
            c = (Calendar)c.clone();
        }
        int y = c.get(Calendar.YEAR);
        int m = c.get(Calendar.MONTH);
        if (m == Calendar.JANUARY ) {
            c.set(Calendar.YEAR, y - 1);
            c.set(Calendar.MONTH, Calendar.DECEMBER);
        } else {
            c.set(Calendar.MONTH, m - 1);
        }
        return c;
    }
    
    /**
     * 先月のデータを持った Calendar を取得します。
     * 取得する Calendar オブジェクトは指定したオブジェクトとは別のものです。
     * 
     * @return 先月のCalendarオブジェクト
     * @since 2.0
     */
    public static Calendar getLastMonth() {        
        return getLastMonth(null);
    }
    
    
    /**
     * 指定したCalendarの来月のデータを持ったCalendarを取得します。
     * 取得するCalendarオブジェクトは指定したオブジェクトとは別のものです。
     * 
     * @param c Calendarオブジェクト。
     *           nullの場合は実行時にCalendarが生成されて利用する
     * @return 来月のCalendarオブジェクト
     * @since 2.0
     */
    public static Calendar getNextMonth(Calendar c) {        
        if (c == null) {
            c = Calendar.getInstance();
        } else {
            c = (Calendar)c.clone();
        }
        int y = c.get(Calendar.YEAR);
        int m = c.get(Calendar.MONTH);
        if (m == Calendar.DECEMBER) {
            c.set(Calendar.YEAR, y + 1);
            c.set(Calendar.MONTH, Calendar.JANUARY);
        } else {
            c.set(Calendar.MONTH, m + 1);
        }
        return c;
    }
    
    /**
     * 来月のデータを持ったCalendarを取得します。
     * 取得するCalendarオブジェクトは指定したオブジェクトとは別のものです。
     * 
     * @return 来月の Calendar オブジェクト
     * @since 2.0
     */
    public static Calendar getNextMonth() {        
        return getNextMonth(null);
    }
    
    /**
     * 先月の日付文字列を指定した形式で取得します。
     * 
     * @param format 日付文字列の形式
     * @return 日付文字列
     * @since 2.0
     */
    public static String getLastMonthString(String format) {
        Calendar c = getLastMonth();
        return getString(c, format);
    }

    /**
     * 先月の日付文字列を取得します。
     * 取得する日付文字列の形式はYYYYMMです。
     * 
     * @return 日付文字列
     * @since 2.0
     */
    public static String getLastMonthString() {
        return getLastMonthString(FORMAT_YYYYMM);
    }
    
    /**
     * 来月の日付文字列を指定した形式で取得します。
     * 
     * @param format 日付文字列の形式
     * @return 日付文字列
     * @since 2.0
     */
    public static String getNextMonthString(String format) {
        Calendar c = getNextMonth();
        return getString(c, format);
    }

    /**
     * 来月の日付文字列を取得します。
     * 取得する日付文字列の形式はYYYYMMです。
     * 
     * @return 日付文字列
     * @since 2.0
     */
    public static String getNextMonthString() {
        return getNextMonthString(FORMAT_YYYYMM);
    }
    
    
    /**
     * 今月の日付文字列を指定した形式で取得します。
     * 
     * @param format 日付文字列の形式
     * @return 日付文字列
     * @since 2.0
     */
    public static String getThisMonthString(String format) {
        return getString(Calendar.getInstance(), format);
    }

    /**
     * 今月の日付文字列を取得します。
     * 取得する日付文字列の形式はYYYYMMDDです。
     * 
     * @return 日付文字列
     * @since 2.0
     */
    public static String getThisMonthString() {
        return getThisMonthString(FORMAT_YYYYMM);
    }

    /**
     * 日本語の曜日文字列から曜日を示す値を取得します。
     * 
     * @param str 日本語の曜日文字列
     * @return 曜日を示す値
     * @since 2.0
     */
    public static int toDayOfWeek(String str) {
        if (str == null) {
            throw new IllegalArgumentException("Day-of-week-string is null.");            
        }
        if ("月".equals(str)) {
            return Calendar.MONDAY;
        } else if ("火".equals(str)) {
            return Calendar.TUESDAY;
        } else if ("水".equals(str)) {
            return Calendar.WEDNESDAY;
        } else if ("木".equals(str)) {
            return Calendar.THURSDAY;
        } else if ("金".equals(str)) {
            return Calendar.FRIDAY;
        } else if ("土".equals(str)) {
            return Calendar.SATURDAY;
        } else if ("日".equals(str)) {
            return Calendar.SUNDAY;
        } else {
            throw new IllegalArgumentException("Day-of-week-string is invalid.");
        }
    }

    /**
     * 日付の文字列の検証を行います。
     * 
     * @param date 検証する日付文字列
     * @param format 日付フォーマット(ただし本クラス定義のフォーマットのみ対応)
     * @return 検証結果
     * @throws DateFormatException 日付文字列がフォーマットと異なる場合
     * @since 2.0
     */
    public static int validate(String date, String format) throws DateFormatException {

        Integer[] iDate = dateSplit(date, format);
        Integer year = iDate[0];
        Integer month = iDate[1];
        Integer day = iDate[2];

        //month check
        if (month != null) {
            int iMonth = month.intValue();
            if (iMonth < MIN_MONTH_OF_YEAR || MAX_MONTH_OF_YEAR < iMonth) {
                return VALIDATE_MONTH_OVERRANGE;
            }
        }

        //day check
        if (day != null) {
            int iDay = day.intValue();
            if (iDay < MIN_DAY_OF_MONTH) {
                return VALIDATE_DAY_OVERRANGE;
            }
            
            if (month == null) {
                if (iDay < MIN_DAY_OF_MONTH || MAX_DAY_OF_MONTH < iDay) {
                    return VALIDATE_DAY_OVERRANGE;
                }

            } else {
                int iMonth = month.intValue();                

                if (iMonth == JANUARY) {                    
                    if (iDay > JANUARY_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == FEBRUARY) {
                    if (year == null) {
                        if (iDay > FEBRUARY_DAYS) {
                            return VALIDATE_DAY_OVERRANGE;
                        }
                    } else {
                        GregorianCalendar gc = new GregorianCalendar();
                        if (gc.isLeapYear(year.intValue())) {
                            if (iDay > FEBRUARY_LEAP_DAYS) {
                                return VALIDATE_DAY_OVERRANGE;
                            }
                        } else {
                            if (iDay > FEBRUARY_NORMAL_DAYS) {
                                return VALIDATE_DAY_OVERRANGE;
                            }
                        }
                    }                    

                } else if (iMonth == MARCH) {
                    if (iDay > MARCH_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == APRIL) {
                    if (iDay > APRIL_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == MAY) {
                    if (iDay > MAY_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == JUNE) {
                    if (iDay > JUNE_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == JULY) {
                    if (iDay > JULY_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == AUGUST) {
                    if (iDay > AUGUST_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == SEPTEMBER) {
                    if (iDay > SEPTEMBER_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == OCTOBER) {
                    if (iDay > OCTOBER_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == NOVEMBER) {
                    if (iDay > NOVEMBER_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }

                } else if (iMonth == DECEMBER) {
                    if (iDay > DECEMBER_DAYS) {
                        return VALIDATE_DAY_OVERRANGE;
                    }
                }
            }
        }

        //正常
        return VALIDATE_NORMAL;
    }

    /**
     * 日付文字列を年,月,日単位に分割します。
     * 
     * @param dateString 日付文字列
     * @param format 日付フォーマット
     * @return 分割した結果
     * @throws DateFormatException 日付文字列がフォーマットと異なる場合
     * @since 2.0
     */    
    private static Integer[] dateSplit(String dateString, String format) throws DateFormatException {
        
        Integer[] ret = null;

        if (format.equals(FORMAT_YYYYMMDD)) {
            if (dateString.length() != FORMAT_YYYYMMDD.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {new Integer(dateString.substring(0,4)),
                        new Integer(dateString.substring(4,6)),
                        new Integer(dateString.substring(6,8))};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }
            
        } else if (format.equals(FORMAT_YYYYMM)) {
            if (dateString.length() != FORMAT_YYYYMM.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {new Integer(dateString.substring(0,4)),
                        new Integer(dateString.substring(4,6)),
                        null};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }

        } else if (format.equals(FORMAT_YYMMDD)) {
            if (dateString.length() != FORMAT_YYMMDD.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {new Integer(dateString.substring(0,2)),
                        new Integer(dateString.substring(2,4)),
                        new Integer(dateString.substring(4,6))};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }

        } else if (format.equals(FORMAT_YYMM)) {
            if (dateString.length() != FORMAT_YYMM.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {new Integer(dateString.substring(0,2)),
                        new Integer(dateString.substring(2,4)),
                        null};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }

        } else if (format.equals(FORMAT_MMDD)) {
            if (dateString.length() != FORMAT_MMDD.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {null,
                        new Integer(dateString.substring(0,2)),
                        new Integer(dateString.substring(2,4))};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }

        } else if (format.equals(FORMAT_YYYY)) {
            if (dateString.length() != FORMAT_YYYY.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {new Integer(dateString),
                        null,
                        null};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }

        } else if (format.equals(FORMAT_YY)) {
            if (dateString.length() != FORMAT_YY.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {new Integer(dateString),
                        null,
                        null};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }

        } else if (format.equals(FORMAT_MM)) {
            if (dateString.length() != FORMAT_MM.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {null,
                        new Integer(dateString),
                        null};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }

        } else if (format.equals(FORMAT_DD)) {
            if (dateString.length() != FORMAT_DD.length()) {
                throw new DateFormatException("date is illegal-length.");
            }
            try {
                ret = new Integer[] {null,
                        null,
                        new Integer(dateString)};
            } catch (NumberFormatException e) {
                throw new DateFormatException("date is illegal-format.");
            }
        } else {
            throw new DateFormatException("format is illegal.");
        }

        return ret;
    }

}
