package core.util;

import java.sql.Time;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Pattern;

import org.apache.logging.log4j.LogManager;

import core.config.Env;
import core.config.Factory;

/**
 * 日付関連操作
 *
 * @author Tadashi Nakayama
 */
public final class DateUtil {

	/** 1日のミリ秒 */
	public static final long A_DAY_MILL = TimeUnit.DAYS.toMillis(1);

	/** 日時フォーマット */
	public static final String FORMAT_DATE_TIME = "yyyyMMddHHmmssSSS";
	/** 日付フォーマット */
	public static final String FORMAT_DATE = "yyyyMMdd";
	/** 時刻フォーマット */
	public static final String FORMAT_TIME = "HHmmss";
	/** 時刻フォーマット */
	public static final String FORMAT_TIME_MILL = "HHmmssSSS";

	/** システム時間 */
	private static final String ENV_SYSTEM_DATETIME = "System.DateTime";

	/** 数字パターン */
	private static final Pattern PATTERN_NUM = Pattern.compile("^[0-9]+$");

	/**
	 * コンストラクタ
	 */
	private DateUtil() {
		throw new AssertionError();
	}

	/**
	 * 日付正規化
	 *
	 * @param val 日付文字列
	 * @return 正規化日付文字列
	 */
	public static String normalizeDate(final String val) {
		var ret = val;
		if (!Objects.toString(val, "").isEmpty() && isAbsolute(val)) {
			final var str = removeSeparator(val);
			if (str.length() == 6) {
				final var pattern = Pattern.compile(".+[0-9]$");
				if (pattern.matcher(val).matches()) {
					ret = str.substring(0, 4) + "0"
							+ str.substring(4, 5) + "0" + str.substring(5);
				}
			} else if (str.length() == 7) {
				ret = str.substring(0, 4) + "0" + str.substring(4);

				final var mid = str.codePointAt(5);
				if (str.codePointAt(4) == '1' && (mid == '0' || mid == '1' || mid == '2')) {
					final var pattern = Pattern.compile(".+[^0-9][0-9]{2}[^0-9]*$");
					if (!pattern.matcher(val).matches()) {
						ret = str.substring(0, 6) + "0" + str.substring(6);
					}
				}
			}
		}
		return ret;
	}

	/**
	 * 複製
	 *
	 * @param <T> ジェネリックス
	 * @param val Dateオブジェクト
	 * @return Dateオブジェクト
	 */
	public static <T extends Date> T copyOf(final T val) {
		return (val != null) ? newDate(val.getClass().asSubclass(Date.class), val.getTime()) : null;
	}

	/**
	 * 現在時刻を返す。
	 *
	 * @return システム日付
	 */
	public static Timestamp getDateTime() {
		var ret = new Timestamp(System.currentTimeMillis());
		final var env = removeSeparator(Env.getEnv(ENV_SYSTEM_DATETIME).trim());
		if (!env.isEmpty() && env.length() <= FORMAT_DATE_TIME.length()) {
			final var sdf = new SimpleDateFormat(FORMAT_DATE_TIME);
			final var str = sdf.format(ret);
			try {
				ret = new Timestamp(sdf.parse(env + str.substring(env.length())).getTime());
			} catch (final ParseException ex) {
				LogManager.getLogger().info(ex.getMessage());
			}
		}
		return ret;
	}

	/**
	 * 日時を返す。
	 *
	 * @param date Dateオブジェクト
	 * @return 変換ご日時オブジェクト
	 */
	public static Timestamp toDateTime(final Date date) {
		return Optional.ofNullable(date).map(d ->
			Timestamp.class.isInstance(d) ? Timestamp.class.cast(d) : new Timestamp(d.getTime())
		).orElse(null);
	}

	/**
	 * 日付を返す。
	 *
	 * @param date Dateオブジェクト
	 * @return 変換後日付オブジェクト
	 */
	public static java.sql.Date toDate(final Date date) {
		final Function<Date, java.sql.Date> func = d -> {
			final var diff = TimeZone.getDefault().getRawOffset();
			final var time = d.getTime() + diff;
			return new java.sql.Date(((time / A_DAY_MILL) * A_DAY_MILL) - diff);
		};

		return Optional.ofNullable(date).map(
			d -> java.sql.Date.class.isInstance(d) ? java.sql.Date.class.cast(d) : func.apply(d)
		).orElse(null);
	}

	/**
	 * 時間を返す。
	 *
	 * @param date Dateオブジェクト
	 * @return 変換後時刻オブジェクト
	 */
	public static Time toTime(final Date date) {
		final Function<Date, Time> func = d -> {
			final var diff = TimeZone.getDefault().getRawOffset();
			return new Time((d.getTime() + diff) % A_DAY_MILL - diff);
		};

		return Optional.ofNullable(date).map(
			d -> Time.class.isInstance(d) ? Time.class.cast(d) : func.apply(d)
		).orElse(null);
	}

	/**
	 * 日時を返す。
	 *
	 * @param date 日時文字列
	 * @return 変換後日時オブジェクト
	 */
	public static Timestamp toDateTime(final String date) {
		final var pad = "000000";
		final var max = FORMAT_DATE_TIME.length();
		final var t = toDateTime(parseDate(date, FORMAT_DATE_TIME));
		final var d = removeSeparator(date);
		if (t != null && max < d.length()) {
			var nano = d.substring(max, max + Math.min(pad.length(), d.length() - max));
			nano = nano + pad.substring(0, pad.length() - nano.length());
			t.setNanos(t.getNanos() + NumberUtil.toInt(nano, 0));
		}
		return t;
	}

	/**
	 * 日付を返す。
	 *
	 * @param date 日付文字列
	 * @return 変換後日付オブジェクト
	 */
	public static java.sql.Date toDate(final String date) {
		return toDate(parseDate(date, FORMAT_DATE));
	}

	/**
	 * 時間を返す。
	 *
	 * @param date 時間文字列
	 * @return 変換後時刻オブジェクト
	 */
	public static Time toTime(final String date) {
		return toTime(parseDate(date, FORMAT_TIME_MILL));
	}

	/**
	 * 日時を返す。
	 *
	 * @param date 日付
	 * @param time 時刻
	 * @return 日時
	 */
	public static Timestamp toDateTime(final Date date, final Date time) {
		return toDateTime(date, time, false);
	}

	/**
	 * 日時を返す。
	 *
	 * @param date 日付
	 * @param time 時刻
	 * @param max 最大時分秒付加フラグ
	 * @return 日時
	 */
	public static Timestamp toDateTime(final Date date, final Date time, final boolean max) {
		Timestamp ret = null;
		if (date != null) {
			final var dt = new SimpleDateFormat(FORMAT_DATE).format(date);
			if (time != null) {
				ret = toDateTime(dt + new SimpleDateFormat(FORMAT_TIME_MILL).format(time));
			} else if (max) {
				ret = toDateTime(dt + "235959999");
			} else {
				ret = toDateTime(dt);
			}
		}
		return ret;
	}

	/**
	 * 日時を返す。
	 *
	 * @param date 日付(yyyyMMdd)
	 * @param time 時刻(HHmm | HHmmss | HHmmssSSS)
	 * @return 日時
	 */
	public static Timestamp toDateTime(final String date, final String time) {
		return toDateTime(date, time, false);
	}

	/**
	 * 日時を返す。
	 *
	 * @param date 日付(yyyyMMdd)
	 * @param time 時刻(HHmm | HHmmss | HHmmssSSS)
	 * @param max 最大時分秒付加フラグ
	 * @return 日時
	 */
	public static Timestamp toDateTime(final String date, final String time, final boolean max) {
		Timestamp ret = null;
		if (!Objects.toString(date, "").isEmpty()) {
			if (!Objects.toString(time, "").isEmpty()) {
				ret = toDateTime(date + time);
			} else if (max) {
				ret = toDateTime(date + "235959999");
			} else {
				ret = toDateTime(date);
			}
		}
		return ret;
	}

	/**
	 * 当月の最終日を取得する。
	 *
	 * @param target 指定年月
	 * @param pattern SimpleDateFormatに従った文字列パターン
	 * @return 末日
	 */
	public static java.sql.Date getLastDate(final String target, final String pattern) {
		return getLastDate(parseDate(target, pattern));
	}

	/**
	 * 当月の初日を取得する。
	 *
	 * @param date 日付
	 * @return 当月の最終日の日付
	 */
	public static java.sql.Date getFirstDate(final Date date) {
		return Optional.ofNullable(date).map(d -> {
			final var cal = Calendar.getInstance();
			cal.setTime(d);
			cal.set(Calendar.DATE, 1);
			return toDate(cal.getTime());
		}).orElse(null);
	}

	/**
	 * 当月の最終日を取得する。
	 *
	 * @param date 日付
	 * @return 当月の最終日の日付
	 */
	public static java.sql.Date getLastDate(final Date date) {
		return Optional.ofNullable(date).map(d -> {
			final var cal = Calendar.getInstance();
			cal.setTime(d);
			cal.set(Calendar.DATE, cal.getActualMaximum(Calendar.DATE));
			return toDate(cal.getTime());
		}).orElse(null);
	}

	/**
	 * 曜日数取得
	 *
	 * @param date 日付
	 * @return 曜日数
	 */
	public static int getWeek(final Date date) {
		return Optional.ofNullable(date).map(d -> {
			final var cal = Calendar.getInstance();
			cal.setTime(d);
			return cal.get(Calendar.DAY_OF_WEEK);
		}).orElse(0);
	}

	/**
	 * 開始日から終了日の日数を取得する。
	 *
	 * @param dateFrom 開始日
	 * @param dateTo 終了日
	 * @return 日数
	 */
	public static int diffDate(final Date dateFrom, final Date dateTo) {
		final var dateF = toDate(dateFrom);
		final var dateT = toDate(dateTo);
		final var time = dateT.getTime() - dateF.getTime();
		return Long.valueOf(time / A_DAY_MILL).intValue();
	}

	/**
	 * 日付整形メソッド
	 *
	 * @param date 整形対象日付
	 * @param format 整形出力フォーマット
	 * @return 整形済日付
	 */
	public static String format(final Date date, final String format) {
		if (date == null || Objects.toString(format, "").isEmpty()) {
			return "";
		}
		final var locale = (format.startsWith("G"))
				? new Locale("ja", "JP", "JP") : Locale.getDefault(Locale.Category.FORMAT);
		return new SimpleDateFormat(format, locale).format(date);
	}

	/**
	 * 日付フォーマット変換
	 *
	 * @param str 日付文字列
	 * @param format 変換後フォーマット
	 * @return date
	 */
	public static String format(final String str, final String format) {
		return format(toDate(str), format);
	}

	/**
	 * 時間内確認
	 *
	 * @param start 開始日時
	 * @param now 現在日時
	 * @param end 終了日時
	 * @return 現在日時が、開始時刻以後でかつ終了時刻以前なら true
	 */
	public static boolean isIn(final Date start, final Date now, final Date end) {
		return (start == null || start.compareTo(now) <= 0)
				&& (end == null || end.compareTo(now) >= 0);
	}

	/**
	 * 年を計算する。
	 *
	 * @param date 計算対象になる日付
	 * @param intYear 加算年値（＋：基準日付の後、－：基準日付の前）
	 * @return String 算出した日付
	 */
	public static Date calcYear(final Date date, final int intYear) {
		return calculate(date, Calendar.YEAR, intYear);
	}

	/**
	 * 月を計算する。
	 *
	 * @param date 計算対象になる日付
	 * @param intMonth 加算月値（＋：基準日付の後、－：基準日付の前）
	 * @return String 算出した日付
	 */
	public static Date calcMonth(final Date date, final int intMonth) {
		return calculate(date, Calendar.MONTH, intMonth);
	}

	/**
	 * 日を計算する。
	 *
	 * @param date 計算対象になる日付
	 * @param intDay 加算日値（＋：基準日付の後、－：基準日付の前）
	 * @return String 算出した日付
	 */
	public static Date calcDay(final Date date, final int intDay) {
		return calculate(date, Calendar.DATE, intDay);
	}

	/**
	 * 時を計算する。
	 *
	 * @param date 計算対象になる日時
	 * @param intHour 加算時間値（＋：基準日時の後、－：基準日時の前）
	 * @return String 算出した日時
	 */
	public static Date calcHour(final Date date, final int intHour) {
		return calculate(date, Calendar.HOUR, intHour);
	}

	/**
	 * 日時を計算する。
	 *
	 * @param date 計算対象になる日時
	 * @param field 計算単位
	 * @param quantity 計算量
	 * @return 計算結果
	 */
	public static Date calculate(final Date date, final int field, final int quantity) {
		return Optional.ofNullable(date).map(d -> {
			final var ca = Calendar.getInstance();
			ca.setTime(d);
			ca.add(field, quantity);
			return newDate(d.getClass(), ca.getTimeInMillis());
		}).orElse(null);
	}

	/**
	 * 文字列化
	 *
	 * @param <T> Dateジェネリックス
	 * @param date 日時
	 * @return 文字列
	 */
	public static <T extends Date> String toString(final T date) {
		String ret = null;
		if (Time.class.isInstance(date)) {
			ret = format(date, FORMAT_TIME_MILL);
		} else if (java.sql.Date.class.isInstance(date)) {
			ret = format(date, FORMAT_DATE);
		} else if (date != null) {
			ret = format(date, FORMAT_DATE_TIME);
		}
		return ret;
	}

	/**
	 * 日時オブジェクト取得
	 *
	 * @param <T> Dateジェネリックス
	 * @param str 文字列
	 * @param cls 指定型
	 * @return 日付オブジェクト
	 */
	public static <T extends Date> T toDateObject(final String str, final Class<T> cls) {
		final Date date;
		if (Time.class.equals(cls)) {
			date = parseDate(str, FORMAT_TIME_MILL);
		} else if (java.sql.Date.class.equals(cls)) {
			date = parseDate(str, FORMAT_DATE);
		} else {
			date = parseDate(str, FORMAT_DATE_TIME);
		}
		return (date != null) ? newDate(cls, date.getTime()) : null;
	}

	/**
	 * 文字列内の'-'':''.'' ''/'を削除する。
	 *
	 * @param target 加工対象の文字列
	 * @return 加工後の文字列
	 */
	public static String removeSeparator(final String target) {
		return Optional.ofNullable(target).
				map(t -> t.replaceAll("[\\- :./+]", "")).orElse(target);
	}

	/**
	 * 文字列をDateオブジェクトに変換します。
	 *
	 * @param date 日付文字列
	 * @param pattern SimpleDateFormatに従った文字列パターン
	 * @return date Dateオブジェクト
	 */
	public static Date parseDate(final String date, final String pattern) {
		if (!Objects.toString(date, "").isEmpty() && !Objects.toString(pattern, "").isEmpty()) {
			try {
				final var d = removeSeparator(date);
				final var p = removeSeparator(pattern);
				final var len = Math.min(d.length(), p.length());
				final var sdf = new SimpleDateFormat(p.substring(0, len));
				sdf.setLenient(false);
				return sdf.parse(d.substring(0, len));
			} catch (final ParseException ex) {
				LogManager.getLogger().info(ex.getMessage());
			}
		}
		return null;
	}

	/**
	 * クラスに対応した日付オブジェクト取得
	 *
	 * @param <T> ジェネリックス
	 * @param cls クラス
	 * @param msec ミリセック
	 * @return 日付オブジェクト
	 */
	private static <T extends Date> T newDate(final Class<T> cls, final long msec) {
		return Factory.construct(Factory.getConstructor(cls, long.class), msec);
	}

	/**
	 * 絶対日付判断
	 *
	 * @param str 文字列
	 * @return 絶対日付の場合 true を返す。
	 */
	public static boolean isAbsolute(final String str) {
		return PATTERN_NUM.matcher(removeSeparator(str)).matches();
	}

	/**
	 * 絶対日付変換
	 *
	 * @param str 変換元文字列
	 * @return 変換後文字列
	 */
	public static String toAbsolute(final String str) {
		return (Objects.toString(str, "").isEmpty() || isAbsolute(str))
				? str : format(new AbsoluteDate(str).toAbsolute(), FORMAT_DATE);
	}

	/**
	 * 絶対日付
	 *
	 * @author Tadashi Nakayama
	 */
	private static final class AbsoluteDate {
		/** 漢数字パタン */
		private static final Pattern PAT_NUM_KAN = Pattern.compile("[再一二三四五六七八九十百千]+");
		/** 全角数字パタン */
		private static final Pattern PAT_NUM_ZEN = Pattern.compile("[１２３４５６７８９０]+");
		/** 数字パターン */
		private static final Pattern PAT_NUM = Pattern.compile("[0-9]+");

		/** フィールドマップ */
		private static final Map<Integer, Integer> FIELD;
		/** 始末マップ */
		private static final Map<Integer, Integer> FORE;
		/** サインマップ */
		private static final Map<Integer, Integer> SIGN;
		/** 漢数字マップ */
		private static final Map<Integer, Integer> NUM;

		/** 対象文字列 */
		private final String string;

		/** フィールド */
		private Integer field;
		/** 始末 */
		private Integer fore;
		/** サイン */
		private Integer sign;

		static {
			final var map = new HashMap<Integer, Integer>();
			put("週", Calendar.WEEK_OF_MONTH, map);
			put("月", Calendar.MONTH, map);
			put("年", Calendar.YEAR, map);
			put("日", Calendar.DATE, map);
			put("Ｑ", Calendar.MONTH, map);
			put("ｑ", Calendar.MONTH, map);
			put("Q", Calendar.MONTH, map);
			put("q", Calendar.MONTH, map);
			put("期", Calendar.MONTH, map);
			FIELD = Collections.unmodifiableMap(map);
		}

		static {
			final var map = new HashMap<Integer, Integer>();
			put("初", -1, map);
			put("始", -1, map);
			put("終", 1, map);
			put("末", 1, map);
			FORE = Collections.unmodifiableMap(map);
		}

		static {
			final var map = new HashMap<Integer, Integer>();
			put("翌", 1, map);
			put("後", 1, map);
			put("次", 1, map);
			put("明", 1, map);
			put("来", 1, map);
			put("先", -1, map);
			put("前", -1, map);
			put("去", -1, map);
			// 10倍は、数字がある場合加算するマーク 一昨年 ＝ 二年前
			put("昨", -10, map);
			put("本", 0, map);
			put("今", 0, map);
			put("当", 0, map);
			SIGN = Collections.unmodifiableMap(map);
		}

		static {
			final var map = new HashMap<Integer, Integer>();
			put("再", 2, map);
			put("一", 1, map);
			put("二", 2, map);
			put("三", 3, map);
			put("四", 4, map);
			put("五", 5, map);
			put("六", 6, map);
			put("七", 7, map);
			put("八", 8, map);
			put("九", 9, map);
			NUM = Collections.unmodifiableMap(map);
		}

		/**
		 * コンストラクタ
		 *
		 * @param val 文字列
		 */
		AbsoluteDate(final String val) {
			this.string = val;
		}

		/**
		 * マップ設定
		 *
		 * @param key キー
		 * @param val 値
		 * @param map マップ
		 */
		private static void put(final String key, final int val, final Map<Integer, Integer> map) {
			map.put(key.codePointAt(0), val);
		}

		/**
		 * 絶対日付化
		 *
		 * @return 絶対日付
		 */
		public Date toAbsolute() {
			parse();

			final var cal = Calendar.getInstance();
			final var f = getField();
			cal.add(FIELD.get(f), getInterval(f));
			setForeEnd(cal, f);
			return cal.getTime();
		}

		/**
		 * インターバル取得
		 *
		 * @param fld フィールド値
		 * @return インターバル
		 */
		private int getInterval(final Integer fld) {
			final var num = getNumber();
			var sig = getSign();
			if (sig <= -10) {
				sig = (sig / 10) + (sig % 10);
				if (0 < num) {
					return sig - num;
				}
			}
			return Math.max(num, 1) * sig * getUnit(fld);
		}

		/**
		 * 単位取得
		 *
		 * @param fld フィールド値
		 * @return 単位
		 */
		private int getUnit(final Integer fld) {
			if (isQuarter(fld)) {
				return 3;
			} else if (isHalf(fld)) {
				return 6;
			}
			return 1;
		}

		/**
		 * 四半期判断
		 *
		 * @param fld フィールド値
		 * @return 四半期の場合 true を返す。
		 */
		private boolean isQuarter(final Integer fld) {
			final var val = fld.intValue();
			return val == 'Ｑ' || val == 'ｑ' || val == 'Q' || val == 'q';
		}

		/**
		 * 半期判断
		 *
		 * @param fld フィールド値
		 * @return 半期の場合 true を返す。
		 */
		private boolean isHalf(final Integer fld) {
			return fld.intValue() == '期';
		}

		/**
		 * 数値取得
		 *
		 * @return 数値
		 */
		private int getNumber() {
			final var mat1 = PAT_NUM.matcher(this.string);
			if (mat1.find()) {
				return NumberUtil.toInt(this.string.substring(mat1.start(), mat1.end()), -1);
			}

			final var mat2 = PAT_NUM_ZEN.matcher(this.string);
			if (mat2.find()) {
				return NumberUtil.toInt(MojiUtil.toHalfCase(
						this.string.substring(mat2.start(), mat2.end()), false, false), -1);
			}

			final var mat3 = PAT_NUM_KAN.matcher(this.string);
			if (mat3.find()) {
				return toNumber(this.string.substring(mat3.start(), mat3.end()));
			}

			return -1;
		}

		/**
		 * 数値化
		 *
		 * @param val 文字列
		 * @return 数値
		 */
		private int toNumber(final String val) {
			final var kurai = new int[4];
			for (var i = 0; i < val.length(); i = val.offsetByCodePoints(i, 1)) {
				if ('十' == val.codePointAt(i)) {
					kurai[1] = kurai[0] * 10;
					kurai[0] = 0;
				} else if ('百' == val.codePointAt(i)) {
					kurai[2] = kurai[0] * 100;
					kurai[0] = 0;
				} else if ('千' == val.codePointAt(i)) {
					kurai[3] = kurai[0] * 1000;
					kurai[0] = 0;
				} else {
					kurai[0] = kurai[0] * 10 + NUM.get(val.codePointAt(i)).intValue();
				}
			}
			return Arrays.stream(kurai).sum();
		}

		/**
		 * サイン取得
		 *
		 * @return サイン
		 */
		private int getSign() {
			return this.sign;
		}

		/**
		 * 始末取得
		 *
		 * @return 始末
		 */
		private int getForeEnd() {
			return this.fore;
		}

		/**
		 * フィールド取得
		 *
		 * @return フィールド
		 */
		private Integer getField() {
			return this.field;
		}

		/**
		 * パース処理
		 */
		private void parse() {
			final var odori = '々';

			this.field = null;
			this.fore = null;
			this.sign = null;

			Integer prev = null;
			var ret = 0;
			for (var i = 0; i < this.string.length(); i = this.string.offsetByCodePoints(i, 1)) {
				final var cp = this.string.codePointAt(i);
				if (SIGN.containsKey(cp)) {
					prev = cp;
					ret = ret + SIGN.get(cp);
				} else if (cp == odori) {
					if (prev != null) {
						ret = ret + SIGN.get(prev);
						prev = null;
					}
				} else if (this.field == null && FIELD.containsKey(cp)) {
					this.field = cp;
				} else if (this.fore == null && FORE.containsKey(cp)) {
					this.fore = FORE.get(cp);
				}
			}

			if (this.field == null) {
				this.field = Integer.valueOf('日');
			}
			if (this.fore == null) {
				this.fore = 0;
			}
			this.sign = ret;
		}

		/**
		 * 始末設定
		 *
		 * @param cal カレンダ
		 * @param fld フィールド値
		 */
		private void setForeEnd(final Calendar cal, final Integer fld) {
			final var fe = getForeEnd();
			final var f = FIELD.get(fld);
			if (Calendar.WEEK_OF_MONTH == f) {
				if (fe < 0) {
					cal.add(Calendar.DATE, -1 * (cal.get(Calendar.DAY_OF_WEEK) - 1));
				} else if (0 < fe) {
					cal.add(Calendar.DATE, 7 - cal.get(Calendar.DAY_OF_WEEK));
				}
			} else if (Calendar.MONTH == f) {
				if (fe < 0) {
					cal.add(Calendar.MONTH, getForeMonth(cal.get(Calendar.MONTH), fld));
					cal.set(Calendar.DATE, 1);
				} else if (0 < fe) {
					cal.add(Calendar.MONTH, getEndMonth(cal.get(Calendar.MONTH), fld));
					cal.set(Calendar.DATE, cal.getActualMaximum(Calendar.DATE));
				}
			} else if (Calendar.YEAR == f) {
				if (fe < 0) {
					cal.set(Calendar.MONTH, 1);
					cal.set(Calendar.DATE, 1);
				} else if (0 < fe) {
					cal.set(Calendar.MONTH, 12);
					cal.set(Calendar.DATE, cal.getActualMaximum(Calendar.DATE));
				}
			}
		}

		/**
		 * 期初調整取得
		 *
		 * @param month 現在月(0-11)
		 * @param fld フィールド値
		 * @return 調整月数
		 */
		private int getForeMonth(final int month, final Integer fld) {
			if (isQuarter(fld)) {
				return (month % 3) * -1;
			} else if (isHalf(fld)) {
				return ((month + 3) % 6) * -1;
			}
			return 0;
		}

		/**
		 * 期末調整取得
		 *
		 * @param month 現在月(0-11)
		 * @param fld フィールド値
		 * @return 調整月数
		 */
		private int getEndMonth(final int month, final Integer fld) {
			if (isQuarter(fld)) {
				return 2 - (month % 3);
			} else if (isHalf(fld)) {
				return 5 - ((month + 3) % 6);
			}
			return 0;
		}
	}
}
