/*
 * Copyright (C) 2008-2009 GLAD!! (ITO Yoshiichi)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package jp.sourceforge.glad.calendar;

import java.io.Serializable;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.sourceforge.glad.calendar.text.ISODateFormat;
import jp.sourceforge.glad.calendar.text.SimpleDateFormat;
import jp.sourceforge.glad.calendar.util.ISOTimeZone;

/**
 * ISO 8601 に準拠した Calendar のラッパーです。
 * <p>
 * 月は、1: 1月、2: 2月、...、12:12月、
 * 曜日は、1: 月曜日、2: 火曜日、...、7: 日曜日で表します。
 * 
 * @author GLAD!!
 * @see jp.sourceforge.glad.calendar.CalendarConsts
 * @see java.util.Calendar
 * @see java.util.GregorianCalendar
 */
public class ISOCalendar implements Serializable, Instant {

    private static final long serialVersionUID = 4546860446254646309L;

    // ---- enums

    /**
     * 日付が存在しない場合の補正方法です。
     */
    public enum CorrectionType {

        /** 当月の末日に補正 (0) */
        PREV_DAY,

        /** 翌月の初日に補正 (1) */
        NEXT_DAY

    }

    // ---- constants

    /** コンストラクタを識別するためのマーク */
    static class Mark {}
    static final Mark MARK = new Mark();

    // ---- fieles

    /** 基準となるカレンダー (1970-01-01T00:00:00Z) */
    static final GregorianCalendar EPOCH =
            adaptToISO(getGregorianCalendar(0L, CalendarConsts.UTC));

    /** 情報を保持する Calendar */
    final GregorianCalendar calendar;

    // ---- constuctors

    /**
     * 現在日時を使用してカレンダーを構築します。
     */
    public ISOCalendar() {
        this(getGregorianCalendar(null), MARK);
    }

    /**
     * 現在日時を使用してカレンダーを構築します。
     * 
     * @param zone タイムゾーン
     */
    public ISOCalendar(TimeZone zone) {
        this(getGregorianCalendar(zone), MARK);
    }

    /**
     * 現在年と、指定された月日を使用してカレンダーを構築します。
     * 
     * @param month 月
     * @param day   日
     */
    public ISOCalendar(int month, int day) {
        this();
        int year = getYear();
        clear();
        setDate(year, month, day);
    }

    /**
     * 現在年と、指定された月日を使用してカレンダーを構築します。
     * 
     * @param month 月
     * @param day   日
     * @param zone  タイムゾーン
     */
    public ISOCalendar(int month, int day, TimeZone zone) {
        this(zone);
        int year = getYear();
        clear();
        setDate(year, month, day);
    }

    /**
     * 指定された年月日を使用してカレンダーを構築します。
     * 
     * @param year  年
     * @param month 月
     * @param day   日
     */
    public ISOCalendar(int year, int month, int day) {
        this(new GregorianCalendar(year, month - 1, day), MARK);
    }

    /**
     * 指定された年月日を使用してカレンダーを構築します。
     * 
     * @param year  年
     * @param month 月
     * @param day   日
     * @param zone  タイムゾーン
     */
    public ISOCalendar(int year, int month, int day, TimeZone zone) {
        this(zone);
        clear();
        setDate(year, month, day);
    }

    /**
     * 指定された年月日、時分を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     */
    public ISOCalendar(
            int year, int month, int day,
            int hour, int minute) {
        this(new GregorianCalendar(year, month - 1, day, hour, minute), MARK);
    }

    /**
     * 指定された年月日、時分を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param zone   タイムゾーン
     */
    public ISOCalendar(
            int year, int month, int day,
            int hour, int minute, TimeZone zone) {
        this(zone);
        clear();
        setDateTime(year, month, day, hour, minute);
    }

    /**
     * 指定された年月日、時分秒を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     */
    public ISOCalendar(
            int year, int month, int day,
            int hour, int minute, int second) {
        this(new GregorianCalendar(
                year, month - 1, day, hour, minute, second), MARK);
    }

    /**
     * 指定された年月日、時分秒を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @param zone   タイムゾーン
     */
    public ISOCalendar(
            int year, int month, int day,
            int hour, int minute, int second, TimeZone zone) {
        this(zone);
        clear();
        setDateTime(year, month, day, hour, minute, second);
    }

    /**
     * 指定された年月日、時分秒、ミリ秒を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @param millis ミリ秒
     */
    public ISOCalendar(
            int year, int month, int day,
            int hour, int minute, int second, int millis) {
        this(year, month, day, hour, minute, second);
        setMillis(millis);
    }

    /**
     * 指定された年月日、時分秒、ミリ秒を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @param millis ミリ秒
     * @param zone   タイムゾーン
     */
    public ISOCalendar(
            int year, int month, int day,
            int hour, int minute, int second, int millis, TimeZone zone) {
        this(year, month, day, hour, minute, second, zone);
        setMillis(millis);
    }

    /**
     * 基準時点からのミリ秒数を使用してカレンダーを構築します。
     * 
     * @param timeInMillis 基準時点からのミリ秒数
     */
    public ISOCalendar(long timeInMillis) {
        this(getGregorianCalendar(timeInMillis, null), MARK);
    }

    /**
     * 基準時点からのミリ秒数を使用してカレンダーを構築します。
     * 
     * @param timeInMillis 基準時点からのミリ秒数
     * @param zone タイムゾーン
     */
    public ISOCalendar(long timeInMillis, TimeZone zone) {
        this(getGregorianCalendar(timeInMillis, zone), MARK);
    }

    /**
     * java.util.Calendar を使用してカレンダーを構築します。
     * 
     * @param calendar java.util.Calendar
     */
    public ISOCalendar(Calendar calendar) {
        this(calendar.getTimeInMillis(), calendar.getTimeZone());
    }

    /**
     * java.util.Date を使用してカレンダーを構築します。
     * 
     * @param date java.util.Date
     */
    public ISOCalendar(java.util.Date date) {
        this(date.getTime());
    }

    /**
     * 時点 (Instant) を使用してカレンダーを構築します。
     * 
     * @param instant 時点
     */
    public ISOCalendar(Instant instant) {
        this(instant.getTimeInMillis(), instant.getTimeZone());
    }

    /**
     * java.util.GregorianCalendar を使用してカレンダーを構築します。
     * <p>
     * 内部的に使用するコンストラクタです。
     * 引数で渡された calendar をコピーせずにそのまま使用します。
     * 暦週などの設定を ISO 8601 の仕様に合わせて変更します。
     * 
     * @param calendar java.util.GregorianCalendar
     * @param mark コンストラクタを識別するためのマーク
     */
    ISOCalendar(GregorianCalendar calendar, Mark mark) {
        this.calendar = adaptToISO(calendar);
    }

    // ----

    static GregorianCalendar getGregorianCalendar(TimeZone zone) {
        if (zone == null) {
            zone = TimeZone.getDefault();
        }
        // TODO テスト用の日付対応。
        return new GregorianCalendar(zone);
    }

    static GregorianCalendar getGregorianCalendar(
            long timeInMillis, TimeZone zone) {
        GregorianCalendar calendar = getGregorianCalendar(zone);
        calendar.setTimeInMillis(timeInMillis);
        return calendar;
    }

    /**
     * GregorianCalendar を ISO 8601 の仕様に適合させます。
     * <pre>
     * JIS X 0301
     * 4.3.2.2 暦週
     *   備考1. 暦年の最初の暦週には、前の暦年の日が最大3日含まれる可能性がある。
     * </pre>
     */
    static GregorianCalendar adaptToISO(GregorianCalendar calendar) {
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.setMinimalDaysInFirstWeek(4);
        return calendar;
    }

    // ---- clear

    /**
     * 値をクリアします。
     * 
     * @return ISOCalendar
     */
    public ISOCalendar clear() {
        calendar.clear();
        return this;
    }

    // ---- Era

    /**
     * 年代を返します。
     * 
     * @return 年代
     */
    public int getEra() {
        return calendar.get(Calendar.ERA);
    }

    /**
     * 年代を設定します。
     * 
     * @param era 年代
     * @return ISOCalendar
     */
    public ISOCalendar setEra(int era) {
        calendar.set(Calendar.ERA, era);
        return this;
    }

    // ---- Year

    /**
     * 年を返します。
     * 
     * @return 年
     */
    public int getYear() {
        return calendar.get(Calendar.YEAR);
    }

    /**
     * 年を設定します。
     * 
     * @param year 年
     * @return ISOCalendar
     */
    public ISOCalendar setYear(int year) {
        calendar.set(Calendar.YEAR, year);
        return this;
    }

    /**
     * 年を設定します。
     * 
     * @param year 年
     * @param correction 日付が存在しない場合の補正方法
     * @return ISOCalendar
     */
    public ISOCalendar setYear(int year, CorrectionType correction) {
        int month = getMonth();
        setYear(year);
        if (getMonth() != month) {
            setDay(correction.ordinal());
        }
        return this;
    }

    /**
     * 年を加算します。
     * 
     * @param years 年数
     * @return ISOCalendar
     */
    public ISOCalendar addYears(int years) {
        calendar.add(Calendar.YEAR, years);
        return this;
    }

    /**
     * 年を加算します。
     * 
     * @param years 年数
     * @param correction 日付が存在しない場合の補正方法
     * @return ISOCalendar
     */
    public ISOCalendar addYears(int years, CorrectionType correction) {
        setYear(getYear() + years, correction);
        return this;
    }

    /**
     * うるう年か判定します。
     * 
     * @return うるう年ならば true
     */
    public boolean isLeapYear() {
        return isLeapYear(getYear());
    }


    /**
     * 指定された年がうるう年か判定します。
     * 
     * @param year 年
     * @return うるう年ならば true
     */
    public static boolean isLeapYear(int year) {
        return EPOCH.isLeapYear(year);
    }

    /**
     * 暦週年を返します。
     * 
     * @return 暦週年
     */
    public int getWeekYear() {
        int year = getYear();
        int month = getMonth();
        if (month == 1) {
            int day = getDay();
            if (day < 4) {
                //  Mon   Tue   Wed   Thu   Fri   Sat   Sun
                // 12-28 12-29 12-30 12-31 01-01 01-02 01-03 -> prev year
                // 01-04 ...
                int dayOfWeek0101 = getDayOfWeek() - day + 1;
                if (dayOfWeek0101 < 1) {
                    dayOfWeek0101 += 7;
                }
                if (CalendarConsts.THURSDAY < dayOfWeek0101) {
                    --year;
                }
            }
        } else if (month == 12) {
            int day = getDay();
            if (28 < day) {
                //  Mon   Tue   Wed   Thu   Fri   Sat   Sun
                //                                 ... 12-28
                // 12-29 12-30 12-31 01-01 01-02 01-03 01-04 -> next year
                int dayOfWeek1231 = getDayOfWeek() + day - 31;
                if (7 < dayOfWeek1231) {
                    dayOfWeek1231 -= 7;
                }
                if (dayOfWeek1231 < CalendarConsts.THURSDAY) {
                    ++year;
                }
            }
        }
        return year;
    }

    /**
     * 暦週年を設定します。
     * 
     * @param weekYear
     * @return ISOCalendar
     */
    public ISOCalendar setWeekYear(int weekYear) {
        setYear(weekYear);
        return this;
    }

    /**
     * 年代の年を返します。
     * 
     * @return 年代の年
     */
    public int getYearOfEra() {
        return calendar.get(Calendar.YEAR);
    }

    /**
     * 年代の年を設定します。
     * 
     * @param yearOfEra 年代の年
     * @return ISOCalendar
     */
    public ISOCalendar setYearOfEra(int yearOfEra) {
        calendar.set(Calendar.YEAR, yearOfEra);
        return this;
    }

    /**
     * 年代の年を設定します。
     * 
     * @param era 年代
     * @param yearOfEra 年
     * @return ISOCalendar
     */
    public ISOCalendar setYearOfEra(int era, int yearOfEra) {
        calendar.set(Calendar.ERA, era);
        calendar.set(Calendar.YEAR, yearOfEra);
        return this;
    }

    // ---- Month

    /**
     * 月を返します。
     * 
     * @return 月 (1: 1月、2: 2月、...、12: 12月)
     */
    public int getMonth() {
        return calendar.get(Calendar.MONTH) + 1;
    }

    /**
     * 月を設定します。
     * 
     * @param month 月 (1: 1月、2: 2月、...、12: 12月)
     * @return ISOCalendar
     */
    public ISOCalendar setMonth(int month) {
        calendar.set(Calendar.MONTH, month - 1);
        return this;
    }

    /**
     * 月を設定します。
     * 
     * @param month 月 (1: 1月、2: 2月、...、12: 12月)
     * @param correction
     * @return ISOCalendar
     */
    public ISOCalendar setMonth(int month, CorrectionType correction) {
        setMonth(month);
        if (getMonth() != month) {
            setDay(correction.ordinal());
        }
        return this;
    }

    /**
     * 月を加算します。
     * 
     * @param months 月数
     * @return ISOCalendar
     */
    public ISOCalendar addMonths(int months) {
        calendar.add(Calendar.MONTH, months);
        return this;
    }

    /**
     * 月を加算します。
     * 
     * @param months 月数
     * @param correction 日付が存在しない場合の補正方法
     * @return ISOCalendar
     */
    public ISOCalendar addMonths(int months, CorrectionType correction) {
        setMonth(getMonth() + months, correction);
        return this;
    }

    /**
     * 年の月を返します。
     * 
     * @return 年の月
     */
    public int getMonthOfYear() {
        return getMonth();
    }

    /**
     * 年の月を設定します。
     * 
     * @param monthOfYear 年の月
     * @return ISOCalendar
     */
    public ISOCalendar setMonthOfYear(int monthOfYear) {
        setMonth(monthOfYear);
        return this;
    }

    // ---- Week

    /**
     * 週を加算します。
     * 
     * @param weeks 週数
     * @return ISOCalendar
     */
    public ISOCalendar addWeeks(int weeks) {
        addDays(weeks * 7);
        return this;
    }

    /**
     * 年の週を返します。
     * 
     * @return 年の週
     */
    public int getWeekOfYear() {
        return calendar.get(Calendar.WEEK_OF_YEAR);
    }

    /**
     * 年の週を設定します。
     * 
     * @param weekOfYear 年の週
     * @return ISOCalendar
     */
    public ISOCalendar setWeekOfYear(int weekOfYear) {
        calendar.set(Calendar.WEEK_OF_YEAR, weekOfYear);
        return this;
    }

    // ---- Day

    /**
     * 日を返します。
     * 
     * @return 日
     */
    public int getDay() {
        return calendar.get(Calendar.DAY_OF_MONTH);
    }

    /**
     * 日を設定します。
     * 
     * @param day 日
     * @return ISOCalendar
     */
    public ISOCalendar setDay(int day) {
        calendar.set(Calendar.DAY_OF_MONTH, day);
        return this;
    }

    /**
     * 日を加算します。
     * 
     * @param days 日数
     * @return ISOCalendar
     */
    public ISOCalendar addDays(int days) {
        calendar.add(Calendar.DAY_OF_MONTH, days);
        return this;
    }

    /**
     * 年間通算日を返します。
     * 
     * @return 年間通算日
     */
    public int getDayOfYear() {
        return calendar.get(Calendar.DAY_OF_YEAR);
    }

    /**
     * 年間通算日を設定します。
     * 
     * @param dayOfYear 年間通算日
     * @return ISOCalendar
     */
    public ISOCalendar setDayOfYear(int dayOfYear) {
        calendar.set(Calendar.DAY_OF_YEAR, dayOfYear);
        return this;
    }

    /**
     * 年の最後の日を返します。
     * 
     * @return 年の最後の日
     */
    public int getLastDayOfYear() {
        return calendar.getActualMaximum(Calendar.DAY_OF_YEAR);
    }

    /**
     * 年の最後の日に設定します。
     * 
     * @return ISOCalendar
     */
    public ISOCalendar setLastDayOfYear() {
        setDayOfYear(getLastDayOfYear());
        return this;
    }

    /**
     * 年の最初の日か判定します。
     * 
     * @return 最初の日ならば true
     */
    public boolean isFirstDayOfYear() {
        return getDayOfYear() == 1;
    }

    /**
     * 年の最後の日か判定します。
     * 
     * @return 最後の日ならば true
     */
    public boolean isLastDayOfYear() {
        return getDayOfYear() == getLastDayOfYear();
    }

    /**
     * 月の日を返します。
     * 
     * @return 月の日
     */
    public int getDayOfMonth() {
        return getDay();
    }

    /**
     * 月の日を設定します。
     * 
     * @param dayOfMonth 月の日
     * @return ISOCalendar
     */
    public ISOCalendar setDayOfMonth(int dayOfMonth) {
        setDay(dayOfMonth);
        return this;
    }

    /**
     * 月の最後の日を返します。
     * 
     * @return 月の最後の日
     */
    public int getLastDayOfMonth() {
        return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
    }

    /**
     * 月の最後の日に設定します。
     * 
     * @return ISOCalendar
     */
    public ISOCalendar setLastDayOfMonth() {
        setDayOfMonth(getLastDayOfMonth());
        return this;
    }

    /**
     * 月の最初の日か判定します。
     * 
     * @return 最初の日ならば true
     */
    public boolean isFirstDayOfMonth() {
        return getDay() == 1;
    }

    /**
     * 月の最後の日か判定します。
     * 
     * @return 最後の日ならば true
     */
    public boolean isLastDayOfMonth() {
        return getDay() == getLastDayOfMonth();
    }

    // ---- Day of Week

    /**
     * 曜日を返します。
     * 
     * @return 曜日 (1: 月曜日、2: 火曜日、...、7: 日曜日)
     */
    public int getDayOfWeek() {
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
        if (dayOfWeek == Calendar.SUNDAY) {
            return CalendarConsts.SUNDAY;
        } else {
            return dayOfWeek - 1;
        }
    }

    /**
     * 曜日を設定します。
     * 
     * @param dayOfWeek 曜日
     * @return ISOCalendar
     */
    public ISOCalendar setDayOfWeek(int dayOfWeek) {
        if (dayOfWeek == CalendarConsts.SUNDAY) {
            calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
        } else {
            calendar.set(Calendar.DAY_OF_WEEK, dayOfWeek + 1);
        }
        return this;
    }

    public String getDayOfWeekText() {
        return new SimpleDateFormat("EEEE").format(calendar);
    }

    public String getDayOfWeekText(Locale locale) {
        return new SimpleDateFormat("EEEE", locale).format(calendar);
    }

    public String getDayOfWeekShortText() {
        return new SimpleDateFormat("EEE").format(calendar);
    }

    public String getDayOfWeekShortText(Locale locale) {
        return new SimpleDateFormat("EEE", locale).format(calendar);
    }

    
    /**
     * 月曜日か判定します。
     * 
     * @return 月曜日ならば true
     */
    public boolean isMonday() {
        return (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY);
    }

    /**
     * 火曜日か判定します。
     * 
     * @return 火曜日ならば true
     */
    public boolean isTuesday() {
        return (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.TUESDAY);
    }

    /**
     * 水曜日か判定します。
     * 
     * @return 水曜日ならば true
     */
    public boolean isWednesday() {
        return (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.WEDNESDAY);
    }

    /**
     * 木曜日か判定します。
     * 
     * @return 木曜日ならば true
     */
    public boolean isThursday() {
        return (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.THURSDAY);
    }

    /**
     * 金曜日か判定します。
     * 
     * @return 金曜日ならば true
     */
    public boolean isFriday() {
        return (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY);
    }

    /**
     * 土曜日か判定します。
     * 
     * @return 土曜日ならば true
     */
    public boolean isSaturday() {
        return (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
    }

    /**
     * 日曜日か判定します。
     * 
     * @return 日曜日ならば true
     */
    public boolean isSunday() {
        return (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
    }

    // ---- Hour

    /**
     * 時を返します。
     * 
     * @return 時
     */
    public int getHour() {
        return calendar.get(Calendar.HOUR_OF_DAY);
    }

    /**
     * 時を設定します。
     * 
     * @param hour 時
     * @return ISOCalendar
     */
    public ISOCalendar setHour(int hour) {
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        return this;
    }

    /**
     * 時を加算します。
     * 
     * @param hours 時数
     * @return ISOCalendar
     */
    public ISOCalendar addHours(int hours) {
        calendar.add(Calendar.HOUR_OF_DAY, hours);
        return this;
    }

    /**
     * 日の時を返します。
     * 
     * @return 日の時
     */
    public int getHourOfDay() {
        return getHour();
    }

    /**
     * 日の時を設定します。
     * 
     * @param hourOfDay 日の時
     * @return ISOCalendar
     */
    public ISOCalendar setHourOfDay(int hourOfDay) {
        setHour(hourOfDay);
        return this;
    }

    // ---- Minute

    /**
     * 分を返します。
     * 
     * @return 分
     */
    public int getMinute() {
        return calendar.get(Calendar.MINUTE);
    }

    /**
     * 分を設定します。
     * 
     * @param minute 分
     * @return ISOCalendar
     */
    public ISOCalendar setMinute(int minute) {
        calendar.set(Calendar.MINUTE, minute);
        return this;
    }

    /**
     * 分を加算します。
     * 
     * @param minutes 分数
     * @return ISOCalendar
     */
    public ISOCalendar addMinutes(int minutes) {
        calendar.add(Calendar.MINUTE, minutes);
        return this;
    }

    /**
     * 日の分を返します。
     * 
     * @return 日の分
     */
    public int getMinuteOfDay() {
        return getHour() * 60 + getMinute();
    }

    /**
     * 日の分を設定します。
     * 
     * @param minuteOfDay 日の分
     * @return ISOCalendar
     */
    public ISOCalendar setMinuteOfDay(int minuteOfDay) {
        int minuteOfHour = minuteOfDay % 60;
        int hourOfDay    = minuteOfDay / 60;
        setTime(hourOfDay, minuteOfHour);
        return this;
    }

    /**
     * 時の分を返します。
     * 
     * @return 時の分
     */
    public int getMinuteOfHour() {
        return getMinute();
    }

    /**
     * 時の分を設定します。
     * 
     * @param minuteOfHour 日の分
     * @return ISOCalendar
     */
    public ISOCalendar setMinuteOfHour(int minuteOfHour) {
        setMinute(minuteOfHour);
        return this;
    }

    // ---- Second

    /**
     * 秒を返します。
     * 
     * @return 秒
     */
    public int getSecond() {
        return calendar.get(Calendar.SECOND);
    }

    /**
     * 秒を設定します。
     * 
     * @param second 秒
     * @return ISOCalendar
     */
    public ISOCalendar setSecond(int second) {
        calendar.set(Calendar.SECOND, second);
        return this;
    }

    /**
     * 秒を加算します。
     * 
     * @param seconds 秒数
     * @return ISOCalendar
     */
    public ISOCalendar addSeconds(int seconds) {
        calendar.add(Calendar.SECOND, seconds);
        return this;
    }

    /**
     * 日の秒を返します。
     * 
     * @return 日の秒
     */
    public int getSecondOfDay() {
        return getMinuteOfDay() * 60 + getSecond();
    }

    /**
     * 日の秒を設定します。
     * 
     * @param secondOfDay 日の秒
     * @return ISOCalendar
     */
    public ISOCalendar setSecondOfDay(int secondOfDay) {
        int secondOfMinute = secondOfDay % 60;
        int minuteOfDay    = secondOfDay / 60;
        int minuteOfHour   = minuteOfDay % 60;
        int hourOfDay      = minuteOfDay / 60;
        setTime(hourOfDay, minuteOfHour, secondOfMinute);
        return this;
    }

    /**
     * 時の秒を返します。
     * 
     * @return 時の秒
     */
    public int getSecondOfHour() {
        return getMinuteOfHour() * 60 + getSecond();
    }

    /**
     * 時の秒を設定します。
     * 
     * @param secondOfHour
     * @return ISOCalendar
     */
    public ISOCalendar setSecondOfHour(int secondOfHour) {
        int secondOfMinute = secondOfHour % 60;
        int minuteOfHour   = secondOfHour / 60;
        setMinute(minuteOfHour);
        setSecond(secondOfMinute);
        return this;
    }

    /**
     * 分の秒を返します。
     * 
     * @return
     */
    public int getSecondOfMinute() {
        return getSecond();
    }

    /**
     * 分の秒を設定します。
     * 
     * @param secondOfMinute 分の秒
     * @return ISOCalendar
     */
    public ISOCalendar setSecondOfMinute(int secondOfMinute) {
        setSecond(secondOfMinute);
        return this;
    }

    // ---- Millisecond

    /**
     * ミリ秒を返します。
     * 
     * @return ミリ秒
     */
    public int getMillis() {
        return calendar.get(Calendar.MILLISECOND);
    }

    /**
     * ミリ秒を設定します。
     * 
     * @param millis ミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar setMillis(int millis) {
        calendar.set(Calendar.MILLISECOND, millis);
        return this;
    }

    /**
     * ミリ秒を加算します。
     * 
     * @param millis ミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar addMillis(int millis) {
        calendar.add(Calendar.MILLISECOND, millis);
        return this;
    }

    /**
     * 日のミリ秒を返します。
     * 
     * @return
     */
    public int getMillisOfDay() {
        return getSecondOfDay() * 1000 + getMillis();
    }

    /**
     * 日のミリ秒を設定します。
     * 
     * @param millisOfDay 日のミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar setMillisOfDay(int millisOfDay) {
        int millisOfSecond = millisOfDay % 1000;
        int secondOfDay    = millisOfDay / 1000;
        int secondOfMinute = secondOfDay % 60;
        int minuteOfDay    = secondOfDay / 60;
        int minuteOfHour   = minuteOfDay % 60;
        int hourOfDay      = minuteOfDay / 60;
        setTime(hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond);
        return this;
    }

    /**
     * 時のミリ秒を返します。
     * 
     * @return 時のミリ秒
     */
    public int getMillisOfHour() {
        return getSecondOfHour() * 1000 * getMillis();
    }

    /**
     * 時のミリ秒を返します。
     * 
     * @param millisOfHour 時のミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar setMillisOfHour(int millisOfHour) {
        int millisOfSecond = millisOfHour % 1000;
        int secondOfHour   = millisOfHour / 1000;
        int secondOfMinute = secondOfHour % 60;
        int minuteOfHour   = secondOfHour / 60;
        setMinute(minuteOfHour);
        setSecond(secondOfMinute);
        setMillis(millisOfSecond);
        return this;
    }

    /**
     * 分のミリ秒を返します。
     * 
     * @return 分のミリ秒
     */
    public int getMillisOfMinute() {
        return getSecondOfMinute() * 1000 * getMillis();
    }

    /**
     * 分のミリ秒を設定します。
     * 
     * @param millisOfMinute 分のミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar setMillisOfMinute(int millisOfMinute) {
        int millisOfSecond = millisOfMinute % 1000;
        int secondOfMinute = millisOfMinute / 1000;
        setSecond(secondOfMinute);
        setMillis(millisOfSecond);
        return this;
    }

    /**
     * 秒のミリ秒を返します。
     * 
     * @return 秒のミリ秒
     */
    public int getMillisOfSecond() {
        return getMillis();
    }

    /**
     * 秒のミリ秒を設定します。
     * 
     * @param millisOfSecond 秒のミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar setMillisOfSecond(int millisOfSecond) {
        setMillis(millisOfSecond);
        return this;
    }

    // ---- Date (YMD)

    /**
     * 日付をまとめて設定します。
     * 
     * @param month 月
     * @param day   日
     * @return ISOCalendar
     */
    public ISOCalendar setDate(int month, int day) {
        setMonth(month);
        setDay(day);
        return this;
    }

    /**
     * 日付をまとめて設定します。
     * 
     * @param year  年
     * @param month 月
     * @param day   日
     * @return ISOCalendar
     */
    public ISOCalendar setDate(int year, int month, int day) {
        calendar.set(year, month - 1, day);
        return this;
    }

    /**
     * 年間通算日をまとめて設定します。
     * 
     * @param year 年
     * @param dayOfYear 年の日
     * @return ISOCalendar
     */
    public ISOCalendar setOrdinalDate(int year, int dayOfYear) {
        setYear(year);
        setDayOfYear(dayOfYear);
        return this;
    }

    /**
     * 暦週日付をまとめて設定します。
     * 
     * @param weekYear 年
     * @param weekOfYear 年の週
     * @param dayOfWeek  週の日
     * @return ISOCalendar
     */
    public ISOCalendar setWeekDate(
            int weekYear, int weekOfYear, int dayOfWeek) {
        setWeekYear(weekYear);
        setWeekOfYear(weekOfYear);
        setDayOfWeek(dayOfWeek);
        return this;
    }

    // ---- Time (HMS)

    /**
     * 時刻をまとめて設定します。
     * 
     * @param hour   時
     * @param minute 分
     * @return ISOCalendar
     */
    public ISOCalendar setTime(int hour, int minute) {
        setHour(hour);
        setMinute(minute);
        return this;
    }

    /**
     * 時刻をまとめて設定します。
     * 
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @return ISOCalendar
     */
    public ISOCalendar setTime(int hour, int minute, int second) {
        setHour(hour);
        setMinute(minute);
        setSecond(second);
        return this;
    }

    /**
     * 時刻をまとめて設定します。
     * 
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @param millis ミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar setTime(int hour, int minute, int second, int millis) {
        setHour(hour);
        setMinute(minute);
        setSecond(second);
        setMillis(millis);
        return this;
    }

    // ---- DateTime (YMDHMS)

    /**
     * 日時をまとめて設定します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @return ISOCalendar
     */
    public ISOCalendar setDateTime(
            int year, int month, int day,
            int hour, int minute) {
        calendar.set(year, month - 1, day, hour, minute);
        return this;
    }

    /**
     * 日時をまとめて設定します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @return ISOCalendar
     */
    public ISOCalendar setDateTime(
            int year, int month, int day,
            int hour, int minute, int second) {
        calendar.set(year, month - 1, day, hour, minute, second);
        return this;
    }

    /**
     * 日時をまとめて設定します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @param millis ミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar setDateTime(
            int year, int month, int day,
            int hour, int minute, int second, int millis) {
        setDateTime(year, month, day, hour, minute, second);
        setMillis(millis);
        return this;
    }

    // ---- TimeInMillis

    /**
     * 基準時点からの通算ミリ秒を返します。
     * 
     * @param 通算ミリ秒
     */
    public long getTimeInMillis() {
        return calendar.getTimeInMillis();
    }

    /**
     * 基準時点からの通算ミリ秒を設定します。
     * 
     * @param timeInMillis 通算ミリ秒
     * @return ISOCalendar
     */
    public ISOCalendar setTimeInMillis(long timeInMillis) {
        calendar.setTimeInMillis(timeInMillis);
        return this;
    }

    // ---- TimeZone

    /**
     * タイムゾーンを返します。
     * 
     * @return タイムゾーン
     */
    public TimeZone getTimeZone() {
        return calendar.getTimeZone();
    }

    static final Pattern zonePattern =
            Pattern.compile("([+-])([0-1]?[0-9])[:]?([0-5][0-0])");

    /**
     * 指定された ID のタイムゾーンを取得します。
     * 
     * @param id タイムゾーンID
     */
    public static TimeZone getTimeZone(String id) {
        if (id == null || id.length() == 0) {
            return TimeZone.getDefault();
        }
        if ("Z".equals(id)) {
            return ISOTimeZone.UTC;
        }
        Matcher m = zonePattern.matcher(id);
        if (m.matches()) {
            int signum  = "-".equals(m.group(1)) ? -1 : 1;
            int hours   = Integer.parseInt(m.group(2));
            int minutes = Integer.parseInt(m.group(3));
            return ISOTimeZone.getInstance(signum, hours, minutes);
        }
        return TimeZone.getTimeZone(id);
    }

    /**
     * タイムゾーンを設定します。
     * 
     * @param zone タイムゾーン
     * @return ISOCalendar
     */
    public ISOCalendar setTimeZone(TimeZone zone) {
        calendar.getTimeInMillis();
        calendar.setTimeZone(zone);
        return this;
    }

    /**
     * 指定された ID のタイムゾーンを設定します。
     * 
     * @param id タイムゾーンID
     * @return ISOCalendar
     */
    public ISOCalendar setTimeZone(String id) {
        setTimeZone(getTimeZone(id));
        return this;
    }

    /**
     * オフセットでタイムゾーンを設定します。
     * 
     * @param offset オフセット (ミリ秒)
     * @return ISOCalendar
     */
    public ISOCalendar setTimeZone(int offset) {
        setTimeZone(ISOTimeZone.getInstance(offset));
        return this;
    }

    /**
     * オフセットでタイムゾーンを設定します。
     * 
     * @param signum  符号
     * @param hours   時数
     * @param minutes 分数
     * @return ISOCalendar
     */
    public ISOCalendar setTimeZone(int signum, int hours, int minutes) {
        setTimeZone(ISOTimeZone.getInstance(signum, hours, minutes));
        return this;
    }

    /**
     * ISOTimeZone を返します。
     * 
     * @return ISOTimeZone
     */
    public ISOTimeZone getISOTimeZone() {
        return ISOTimeZone.getInstance(calendar);
    }

    /**
     * タイムゾーンのオフセットを返します。
     * 
     * @return UTC からのオフセット (ミリ秒)
     */
    public int getTimeZoneOffset() {
        return getTimeZone().getOffset(getTimeInMillis());
    }

    // ---- java.util.Calendar

    /**
     * Calendar に変換します。
     * 
     * @return Calendar
     */
    public Calendar toCalendar() {
        Calendar calendar = Calendar.getInstance(getTimeZone());
        calendar.setTimeInMillis(getTimeInMillis());
        return calendar;
    }

    /**
     * Calendar に変換します。
     * 
     * @param locale ロケール
     * @return Calendar
     */
    public Calendar toCalendar(Locale locale) {
        Calendar calendar = Calendar.getInstance(getTimeZone(), locale);
        calendar.setTimeInMillis(getTimeInMillis());
        return calendar;
    }

    /**
     * Calendar を設定します。
     * 
     * @param calendar Calendar
     * @return ISOCalendar
     */
    public ISOCalendar setCalendar(Calendar calendar) {
        setTimeZone(calendar.getTimeZone());
        setTimeInMillis(calendar.getTimeInMillis());
        return this;
    }

    /**
     * GregorianCalendar に変換します。
     * 
     * @return GregorianCalendar
     */
    public GregorianCalendar toGregorianCalendar() {
        return (GregorianCalendar) calendar.clone();
    }

    // ---- java.util.Date

    /**
     * java.util.Date に変換します。
     * 
     * @return java.util.Date
     */
    public java.util.Date toDate() {
        return calendar.getTime();
    }

    /**
     * java.util.Date で値を設定します。
     * 
     * @param date java.util.Date
     * @return ISOCalendar
     */
    public ISOCalendar setDate(java.util.Date date) {
        calendar.setTime(date);
        return this;
    }

    // ---- java.sql.Date

    /**
     * java.sql.Date に変換します。
     * 
     * @return java.sql.Date
     */
    public java.sql.Date toSqlDate() {
        Calendar date = new GregorianCalendar(
                calendar.get(Calendar.YEAR),
                calendar.get(Calendar.MONTH),
                calendar.get(Calendar.DAY_OF_MONTH));
        return new java.sql.Date(date.getTimeInMillis());
    }

    // ---- java.sql.Time

    /**
     * java.sql.Time に変換します。
     * 
     * @return java.sql.Time
     */
    public java.sql.Time toSqlTime() {
        Calendar time = toGregorianCalendar();
        time.set(1970, Calendar.JANUARY, 1);
        return new java.sql.Time(time.getTimeInMillis());
    }

    // ---- Timestamp

    /**
     * java.sql.Timestamp に変換します。
     * 
     * @return java.sql.Timestamp
     */
    public Timestamp toTimestamp() {
        return new Timestamp(getTimeInMillis());
    }

    /**
     * java.sql.Timestamp に変換します。
     * 
     * @param nanos ナノ秒
     * @return java.sql.Timestamp
     */
    public Timestamp toTimestamp(int nanos) {
        Timestamp timestamp = toTimestamp();
        timestamp.setNanos(nanos);
        return timestamp;
    }

    // ---- format

    /**
     * カレンダーをフォーマットします。
     * 
     * @param pattern パターン
     * @return 文字列
     */
    public String format(String pattern) {
        return new ISODateFormat(pattern).format(calendar);
    }

    /**
     * 日付部分をフォーマットします。
     * 
     * @return 日付部分を表す文字列
     */
    public String formatDate() {
        return ISODateFormat.date().format(calendar);
    }

    /**
     * 時刻部分をフォーマットします。
     * 
     * @return 時刻部分を表す文字列
     */
    public String formatTime() {
        return new ISODateFormat("HH:mm:ss.SSS").format(calendar);
    }

    /**
     * 時刻部分をフォーマットします。
     * 
     * @param withTimeZone タイムゾーンを出力するか
     * @return 時刻部分を表す文字列
     */
    public String formatTime(boolean withTimeZone) {
        if (withTimeZone) {
            return ISODateFormat.time().format(calendar);
        } else {
            return formatTime();
        }
    }

    /**
     * 日時をフォーマットします。
     * 
     * @return 日時を表す文字列
     */
    public String formatDateTime() {
        return new ISODateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
                .format(calendar);
    }

    /**
     * 日時をフォーマットします。
     * 
     * @param withTimeZone タイムゾーンを出力するか
     * @return 日時を表す文字列
     */
    public String formatDateTime(boolean withTimeZone) {
        if (withTimeZone) {
            return ISODateFormat.dateTime().format(calendar);
        } else {
            return formatDateTime();
        }
    }

    /**
     * タイムゾーンをフォーマットします。
     * 
     * @return 文字列
     */
    public String formatTimeZone() {
        return getISOTimeZone().getID();
    }

    // ---- parse

    /**
     * 文字列をカレンダーとして解析します。
     * 
     * @param pattern パターン
     * @param s 文字列
     * @return カレンダー
     */
    public static ISOCalendar parse(String pattern, String s) {
        ISODateFormat df = new ISODateFormat(pattern);
        df.setLenient(false);
        try {
            df.parse(s);
            return new ISOCalendar(df.getCalendar());
        } catch (ParseException e) {
            throw newIllegalArgumentException(s, e);
        }
    }

    /**
     * 文字列をカレンダーとして解析します。
     * 
     * @param pattern パターン
     * @param s 文字列
     * @param zone タイムゾーン
     * @return カレンダー
     */
    public static ISOCalendar parse(String pattern, String s, TimeZone zone) {
        DateFormat df = new ISODateFormat(pattern);
        df.setTimeZone(zone);
        df.setLenient(false);
        try {
            df.parse(s);
            return new ISOCalendar(df.getCalendar());
        } catch (ParseException e) {
            throw newIllegalArgumentException(s, e);
        }
    }

    public static ISOCalendar parseDate(String s) {
        return parse("yyyy-MM-dd", s);
    }

    public static ISOCalendar parseTime(String s) {
        return parse("HH:mm:ss.SSS", s);
    }

    public static ISOCalendar parseDateTime(String s) {
        return parse("yyyy-MM-dd'T'HH:mm:ss.SSS", s);
    }

    static IllegalArgumentException newIllegalArgumentException(
            String message, Throwable cause) {
        IllegalArgumentException e = new IllegalArgumentException(message);
        e.initCause(cause);
        return e;
    }

    // ---- java.lang.Object

    /**
     * オブジェクトの文字列表現を返します。
     * 
     * @return 文字列
     */
    @Override
    public String toString() {
        return formatDateTime(true);
    }

    /**
     * ハッシュコードを返します。
     * 
     * @return ハッシュコード
     */
    @Override
    public int hashCode() {
        return hashCode(getTimeInMillis());
    }

    static int hashCode(long value) {
        return (int) (value ^ (value >>> 32));
    }

    /**
     * 他のオブジェクトと等しいか判定します。
     * 
     * @param other 他のオブジェクト
     * @return 等しければ true
     */
    @Override
    public boolean equals(Object other) {
        if ((other instanceof ISOCalendar)) {
            return false;
        }
        return equals((ISOCalendar) other);
    }

    /**
     * 他のカレンダーと等しいか判定します。
     * 
     * @param other 他のカレンダー
     * @return 等しければ true
     */
    public boolean equals(ISOCalendar other) {
        if (other == null) {
            return false;
        }
        return getTimeZone().equals(other.getTimeZone())
                && (getTimeInMillis() == other.getTimeInMillis());
    }

    // ---- java.lang.Comparable

    /**
     * 他の通算ミリ秒と比較します。
     * 
     * @param other 他の通算ミリ秒
     * @return this < other ならば負の整数、
     *         this = other ならば0、
     *         this > other ならば正の整数
     */
    public int compareTo(long other) {
        return compare(getTimeInMillis(), other);
    }

    /**
     * 他のカレンダーと比較します。
     * 
     * @param other 他のカレンダー
     * @return this < other ならば負の整数、
     *         this = other ならば0、
     *         this > other ならば正の整数
     */
    public int compareTo(Calendar other) {
        return compareTo(other.getTimeInMillis());
    }

    /**
     * 他の日付と比較します。
     * 
     * @param other 他の日付
     * @return this < other ならば負の整数、
     *         this = other ならば0、
     *         this > other ならば正の整数
     */
    public int compareTo(java.util.Date other) {
        return compareTo(other.getTime());
    }

    /**
     * 他の時点と比較します。
     * 
     * @param other 他の時点
     * @return this < other ならば負の整数、
     *         this = other ならば0、
     *         this > other ならば正の整数
     */
    public int compareTo(Instant other) {
        return compareTo(other.getTimeInMillis());
    }

    static int compare(long n1, long n2) {
        return (n1 < n2) ? -1 : (n1 == n2) ? 0 : 1;
    }

    /**
     * 他の通算ミリ秒より前か判定します。
     * 
     * @param other 他の通算ミリ秒
     * @return 前ならば true
     */
    public boolean isBefore(long other) {
        return compareTo(other) < 0;
    }

    /**
     * 他のカレンダーより前か判定します。
     * 
     * @param other 他のカレンダー
     * @return 前ならば true
     */
    public boolean isBefore(Calendar other) {
        return compareTo(other) < 0;
    }

    /**
     * 他の日付より前か判定します。
     * 
     * @param other 他の日付
     * @return 前ならば true
     */
    public boolean isBefore(java.util.Date other) {
        return compareTo(other) < 0;
    }

    /**
     * 他の時点より前か判定します。
     * 
     * @param other 他の時点
     * @return 前ならば true
     */
    public boolean isBefore(Instant other) {
        return compareTo(other) < 0;
    }

    /**
     * 他の通算ミリ秒と同時か判定します。
     * 
     * @param other 他の通算ミリ秒
     * @return 同時ならば true
     */
    public boolean isEqual(long other) {
        return compareTo(other) == 0;
    }

    /**
     * 他のカレンダーと同時か判定します。
     * 
     * @param other 他のカレンダー
     * @return 同時ならば true
     */
    public boolean isEqual(Calendar other) {
        return compareTo(other) == 0;
    }

    /**
     * 他の日付と同時か判定します。
     * 
     * @param other 他の日付
     * @return 同時ならば true
     */
    public boolean isEqual(java.util.Date other) {
        return compareTo(other) == 0;
    }

    /**
     * 他の時点と同時か判定します。
     * 
     * @param other 他の時点
     * @return 同時ならば true
     */
    public boolean isEqual(Instant other) {
        return compareTo(other) == 0;
    }

    /**
     * 他の通算ミリ秒より後か判定します。
     * 
     * @param other 他の通算ミリ秒
     * @return 後ならば true
     */
    public boolean isAfter(long other) {
        return compareTo(other) > 0;
    }

    /**
     * 他のカレンダーより後か判定します。
     * 
     * @param other 他のカレンダー
     * @return 後ならば true
     */
    public boolean isAfter(Calendar other) {
        return compareTo(other) > 0;
    }

    /**
     * 他の日付より後か判定します。
     * 
     * @param other 他の日付
     * @return 後ならば true
     */
    public boolean isAfter(java.util.Date other) {
        return compareTo(other) > 0;
    }

    /**
     * 他の時点より後か判定します。
     * 
     * @param other 他の時点
     * @return 後ならば true
     */
    public boolean isAfter(Instant other) {
        return compareTo(other) > 0;
    }

}
