/*
 * Copyright (c) 2009 The openGion Project.
 *
 * 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 org.opengion.fukurou.util;

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import java.util.Calendar;
// import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.HashMap;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;

import org.opengion.fukurou.system.DateSet;								// 6.4.2.0 (2016/01/29)
import static org.opengion.fukurou.system.HybsConst.BUFFER_SMALL;		// 6.1.0.0 (2014/12/26) refactoring

/**
 * HybsDateUtil.java は、共通的に使用される Date,Calender関連メソッドを集約した、staticメソッドのみで構成されるクラスです。
 *
 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
 *
 * @og.group ユーティリティ
 *
 * @version  5.5
 * @author	 Kazuhiko Hasegawa
 * @since    JDK7.0,
 */
public final class HybsDateUtil {

	/** 各種フォーマットを簡易的に表した文字列 */
	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,String>	DATE_FORMAT = new HashMap<>();
	static {
		DATE_FORMAT.put( "YMD"		,"yyyyMMdd"				);
		DATE_FORMAT.put( "Y2MD"		,"yyMMdd"				);
		DATE_FORMAT.put( "YM"		,"yyyyMM"				);
		DATE_FORMAT.put( "MD"		,"MMdd"					);	// 5.5.5.2 (2012/08/18)
		DATE_FORMAT.put( "HMS"		,"HHmmss"				);
		DATE_FORMAT.put( "YMDHMS"	,"yyyyMMddHHmmss"		);
		DATE_FORMAT.put( "EEE"		,"EEE"					);
		DATE_FORMAT.put( "YMDF"		,"yyyy/MM/dd"			);
		DATE_FORMAT.put( "Y2MDF"	,"yy/MM/dd" 			);
		DATE_FORMAT.put( "YMF"		,"yyyy/MM"				);
		DATE_FORMAT.put( "HMSF"		,"HH:mm:ss" 			);
		DATE_FORMAT.put( "YMDHMSF"	,"yyyy/MM/dd HH:mm:ss"	);
		DATE_FORMAT.put( "MDF"		,"MM/dd"				); // 5.5.0.2 (2012/03/09) 和暦
		DATE_FORMAT.put( "MDEF"		,"MM/dd(EEE)"			); // 5.5.0.2 (2012/03/09) 和暦
		DATE_FORMAT.put( "MD2F"		,"MM月dd日"				); // 								5.5.5.2 (2012/08/18) 和暦
		DATE_FORMAT.put( "GYMDF"	,"GGGGyyyy年MM月dd日"	); // 5.5.0.2 (2012/03/09) 和暦
		DATE_FORMAT.put( "G2YMDF"	,"Gyyyy/MM/dd"			); // 5.5.0.2 (2012/03/09) 和暦
		DATE_FORMAT.put( "GYMF"		,"GGGGyyyy年MM月"		); // 5.5.0.2 (2012/03/09) 和暦
		DATE_FORMAT.put( "GYF"		,"GGGGyyyy" 			); // 5.5.0.2 (2012/03/09) 和暦
	}

	/**
	 *	デフォルトコンストラクターをprivateにして、
	 *	オブジェクトの生成をさせないようにする。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 *
	 */
	private HybsDateUtil() {}

//	/**
//	 * 現在日付、時刻を指定のフォーマットで文字列に変換して返します。
//	 * 出力フォーマットは、"yyyy/MM/dd HH:mm:ss" 固定です。
//	 *
//	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
//	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
//	 *
//	 * @return	現在日付、時刻 ( 例 2012/09/05 18:10:24 )
//	 * @og.rtnNotNull
//	 */
//	public static String getDate() {
//		final DateFormat formatter = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss",Locale.JAPAN );
//		return formatter.format( new Date() );
//	}
//
//	/**
//	 * 現在時刻を指定のフォーマットで文字列に変換して返します。
//	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
//	 * 変換時のロケーションは、Locale.JAPAN です。
//	 * 現在時刻は、new Date() で求めます。
//	 *
//	 * @param	form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss.SSS" )
//	 *
//	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
//	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
//	 *
//	 * @return	現在日付、時刻
//	 * @og.rtnNotNull
//	 * @see		java.text.SimpleDateFormat
//	 */
//	public static String getDate( final String form ) {
//		final DateFormat formatter = new SimpleDateFormat( form,Locale.JAPAN );
//		return formatter.format( new Date() );
//	}
//
//	/**
//	 * 指定時刻を指定のフォーマットで文字列に変換して返します。
//	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
//	 * 変換時のロケーションは、Locale.JAPAN です。
//	 * 指定時刻は、new Date( time ) で求めます。
//	 *
//	 * @param	time 指定のカレントタイムのロング値
//	 * @param	form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss.SSS" )
//	 *
//	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
//	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
//	 *
//	 * @return	現在日付、時刻( 例 2001/04/17 15:48:22 )
//	 * @og.rtnNotNull
//	 */
//	public static String getDate( final long time,final String form ) {
//		final DateFormat formatter = new SimpleDateFormat( form,Locale.JAPAN );
//		return formatter.format( new Date( time ) );
//	}

	/**
	 * 指定の文字列から、以下の文字を削除した文字列を返します。
	 * '/' , '-' , ' ' , ':' の数字以外の文字を含むフォーマットされた
	 * 日付文字列を、日付データだけに変換する場合に利用することを想定しています。
	 * よって、マイナス記号や、小数点、コンマなども削除されます。
	 * このメソッドでは、日付としての整合性や桁チェックは行いませんが、
	 * 桁数は、6桁、8桁、14桁のどれかに、合わせます。
	 * 「yyyy/MM/dd HH:mm:ss」 形式を基準としますが、「yyyy/M」「yyyy/M/d」「yy/M/d」「M/d」
	 * 「HH:mm:ss」「H:m」形式にも、対応します。
	 * "/" が、"-" に変更されているケースも対応可能ですが、月/年 形式や、英語、日本語の
	 * 月表示には未対応です。
	 * 
	 * 引数が、null の場合は、ゼロ文字列に、変換します。
	 *
	 * ※ 6.0.2.5 (2014/10/31) 桁数チェックだけは行います。
	 *   桁数は、6桁、8桁、14桁のどれかに、合わせます。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 * @og.rev 5.5.8.3 (2012/11/17) 数字のみ返す仕様だったが、対象以外の文字入力はそのまま返すよう変更
	 * @og.rev 6.0.2.5 (2014/10/31) 簡易的な桁数チェックだけは行います。
	 * @og.rev 6.2.3.0 (2015/05/01) 内部処理を大幅に変更します。
	 *
	 * @param	value 任意の文字列（例：2001/04/17 15:48:22）
	 *
	 * @return	数字だけで構成される文字列（例：20010417154822）(nullはゼロ文字列を返します)
	 * @og.rtnNotNull
	 */
	public static String parseNumber( final String value ) {
		if( value == null || value.isEmpty() ) { return ""; }

		// 年 や、年月日 が省略された場合は、実行日をセットする。
//		final String today = getDate( "yyyyMMdd" );
		final String today = DateSet.getDate( "yyyyMMdd" );

		String val = value.trim();
		val = val.replaceAll( "-" , "/" );			// yyyy-MM-dd 形式を、yyyy/MM/dd 形式にする。

		final int ad = val.indexOf( ' ' ) ;
		String ymd = val  ;
		String hms = null ;

		if( ad > 0 ) {						// スペースがあれば、年月日 と 時分秒 に別れる。
			ymd = val.substring( 0,ad );
			hms = val.substring( ad+1 );
		}
		else if( val.indexOf( ':' ) > 0 ) {
			ymd = today;					// 年月日 は今日になる。
			hms = val;
		}

		final StringBuilder buf = new StringBuilder( BUFFER_SMALL );

		if( ymd != null ) {
			final String[] ymdSp = ymd.split( "/" );
			switch( ymdSp.length ) {
				case 1 : buf.append( ymdSp[0] );	break;					// "/" が存在しない。
				case 2 : if( ymdSp[0].length() < 4 ) {						// MM/dd のケース。yy/MM は想定外
							buf.append( today.substring( 0,4 ) );			// yyyy の年を設定
						 }
						 buf.append( addZero( ymdSp[0] ) ).append( addZero( ymdSp[1] ) );	break;
				default:  if( ymdSp[0].length() == 2 ) {					// yy/MM/dd のケースを想定
							buf.append( today.substring( 0,2 ) );			// yy の年の先頭２桁を設定
						 }
						 buf.append( ymdSp[0] )
							.append( addZero( ymdSp[1] ) )
							.append( addZero( ymdSp[2] ) );	break;
			}
		}
		if( hms != null ) {
			final String[] hmsSp = hms.split( ":" );						// HH:mm:ss
			switch( hmsSp.length ) {
				case 1 : buf.append( hmsSp[0] );			break;			// ":" が存在しない。
				case 2 : buf.append( addZero( hmsSp[0] ) )					// HH:mm のケース。mm:ss は想定外
							.append( addZero( hmsSp[1] ) )
							.append( "00" );				break;
				default: buf.append( addZero( hmsSp[0] ) )					// HH:mm:ss のケースを想定
							.append( addZero( hmsSp[1] ) )
							.append( addZero( hmsSp[2] ) );	break;
			}
		}

		return buf.toString();
	}

	/**
	 * 指定の文字列が、一桁の場合、先頭に 0 を追加します。
	 *
	 * これは、3/4 の様な日付を、0304 にする場合のサポートメソッドです。
	 *
	 * @og.rev 6.2.3.0 (2015/05/01) 新規追加
	 *
	 * @param	val 任意の文字列
	 *
	 * @return	一桁の場合、先頭に 0 を追加
	 */
	private static String addZero( final String val ) {
		return val.length() == 1 ? "0" + val : val ;
	}

	/**
	 * 指定の文字列から、yyyy-mm-dd hh:mm:ss 形式の文字列を作成します。
	 *
	 * これは、java.sql.Timestamp オブジェクトを文字列から作成するに当たり、
	 * Timestamp の文字列形式にしなければならないためです。
	 * 桁数は、８桁 または、１４桁以外の場合は、変換エラーとします。
	 *
	 * @og.rev 5.5.8.5 (2012/11/27) 新規作成
	 *
	 * @param	value 任意の文字列（例：20010417 or 20010417154822）
	 *
	 * @return	Timestampの文字列形式（例：2001-04-17 00:00:00 or 2001-04-17 15:48:22）
	 */
	public static String parseTimestamp( final String value ) {
		if( value == null || ( value.length() != 8 && value.length() != 14 ) ) {
			final String errMsg = "日付文字列は、８桁 または、１４桁で指定してください。"
						+ " value=[" + value + "]" ;
			throw new OgRuntimeException( errMsg );
		}

		// 6.0.2.5 (2014/10/31) char を append する。
		final StringBuilder buf = new StringBuilder( BUFFER_SMALL )
			.append( value.substring( 0,4 ) ).append( '-' )
			.append( value.substring( 4,6 ) ).append( '-' )
			.append( value.substring( 6,8 ) ).append( ' ' );
		if( value.length() == 8 ) {
			buf.append( "00:00:00" );
		}
		else {
			buf.append( value.substring( 8,10  ) ).append( ':' )
				.append( value.substring( 10,12 ) ).append( ':' )
				.append( value.substring( 12,14 ) );
		}

		return buf.toString();
	}

	/**
	 * 日付文字列の桁数の整合性を取ります。
	 * これは、内部で、parseNumber(String) 処理により、不要なフォーマット記号を削除します。
	 * ここでは、基本的には、６文字（yyyyMM）、８文字（yyyyMMdd）、１４文字（yyyyMMddHHmmss）
	 * の日付文字列を作成することを想定していますが、指定の桁数以外は、エラーになります。
	 * 
	 * 引数が、null         ⇒ 桁数に無関係に、空文字列を返す。
	 * 引数の桁数が一致     ⇒ その値を返す。
	 * 引数の桁数が不一致   ⇒ エラー
	 * ただし、引数の最大長は、１４ケタに制限しています。
	 * 
	 * このメソッドでは、日付として成立しているかどうか（99999999など）は判定していません。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) メソッドの内容を移す。
	 *
	 * @param	value   任意の日付け文字列
	 * @param	size    変換したい桁数
	 *
	 * @return	数字だけで構成される文字列（例：20010417154822）(nullはゼロ文字列を返します)
	 * @og.rtnNotNull
	 */
	public static String parseDate( final String value , final int size ) {
		return parseDate( value , size , size );		// 最小と最大を同じ値にする。
	}

	/**
	 * 日付文字列の桁数の整合性を取ります。
	 * これは、内部で、parseNumber(String) 処理により、不要なフォーマット記号を削除します。
	 * ここでは、基本的には、６文字（yyyyMM）、８文字（yyyyMMdd）、１４文字（yyyyMMddHHmmss）
	 * の日付文字列を作成することを想定していますが、それ以外の桁数でも下記のルールに従って
	 * 処理されます。
	 * 
	 * 引数が、null         ⇒ 桁数に無関係に、空文字列を返す。
	 * 引数の桁数が範囲内   ⇒ 以下の処理を実行する。
	 * 引数の桁数を同じ     ⇒ そのまま返す。
	 * 引数の桁数より大きい ⇒ 余をカットして、引数の最大長にそろえる。
	 * 引数の桁数に足りない ⇒ "20000101000000" の文字列の部分文字列を結合させて、引数の最大長にそろえる。
	 * ただし、引数の最大長は、１４ケタに制限しています。
	 * 
	 * このメソッドでは、日付として成立しているかどうか（99999999など）は判定していません。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 * @og.rev 5.6.1.1 (2013/02/08) 桁数チェック導入。６桁以下だとエラーにする。
	 * @og.rev 5.6.6.0 (2013/07/05) 桁数チェックの最小-最大指定
	 * @og.rev 6.2.3.0 (2015/05/01) len == maxSize のとき、パース文字列ではなく、元の値を返していた。
	 *
	 * @param	value   任意の日付け文字列
	 * @param	minSize 変換したい桁数の最小値
	 * @param	maxSize 変換したい桁数の最大値
	 *
	 * @return	数字だけで構成される文字列（例：20010417154822）(nullはゼロ文字列を返します)
	 * @og.rtnNotNull
	 */
	public static String parseDate( final String value , final int minSize , final int maxSize ) {
		if( value == null ) { return ""; }

		// 引数の最大長は、１４ケタに制限しています。
		if( maxSize > 14 ) {
			final String errMsg = "日付登録に許可できる最大桁数は、１４ケタです。"
						+ " maxSize=[" + maxSize + "]" ;
			throw new OgRuntimeException( errMsg );
		}

		final String rtn = parseNumber( value );
		final int len = rtn.length() ;

		if( len < minSize || len > maxSize ) {
			final String errMsg = "日付文字列は、最小["
						+ minSize + "] から、最大[" + maxSize + "]の範囲で指定してください。"
						+ " value=[" + value + "]" ;
			throw new OgRuntimeException( errMsg );
		}

		return rtn ;
	}

	/**
	 * 日付文字列の厳密な整合性チェックを行います。
	 * ここで指定できるのは、８文字（yyyyMMdd）、１４文字（yyyyMMddHHmmss）のどちらかの
	 * 数字だけの日付文字列であり、それが、日付として正しいかどうかのチェックを行います。
	 * 正しければ、true を、間違っていれば、false を返します。
	 * ここでは、20120230（２月３０日）などの日付や、20120101235960 なども false になります。
	 * 引数が、null および、空文字列の場合も、false を返しますので、避けたい場合は、事前に
	 * 判定しておいてください。
	 *
	 * 内部処理としては、DateFormat で、setLenient( false ) を設定することで、
	 * 日付/時刻解析を厳密に解析するにして、ParseException が発生しないかどうか判定しています。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 *
	 * @param	value  数字だけで構成される日付け文字列
	 *
	 * @return	true:日付として正しい場合/false:日付として間違っている場合
	 */
	public static boolean isStrict( final String value ) {
		if( value == null || ( value.length() != 8 && value.length() != 14 ) ) { return false; }

		// 日付の厳密なチェック
		final String form = (value.length() == 8) ? "yyyyMMdd" : "yyyyMMddHHmmss" ;
		final DateFormat formatter = new SimpleDateFormat( form,Locale.JAPAN );
		formatter.setLenient( false );		// 日付/時刻解析を厳密に行う(false=厳密)

		boolean flag ;
		try {
			formatter.parse( value );
			flag = true;
		}
		catch( final ParseException ex ) {
			flag = false;
		}

		return flag;
	}

	/**
	 * 日付関係の情報を簡易的に処理します。
	 *
	 * CC引数の加減算パラメータは、0 です。
	 *
	 * @og.rev 5.7.4.1 (2014/03/14) CC 引数を拡張するため、旧メソッドを再現しておきます。
	 *
	 * @param   key		フォーマットの予約語
	 * @param   prmA	基準となる日付(nullの場合は、処理時刻)
	 * @param   prmB	処理コマンド
	 *
	 * @return   メッセージ情報
	 * @og.rtnNotNull
	 * @see		#getDateFormat( String , String ,String , int )
	 */
	public static String getDateFormat( final String key ,final String prmA ,final String prmB ) {
		return getDateFormat( key,prmA,prmB,0 );
	}

	/**
	 * 日付関係の情報を簡易的に処理します。
	 *
	 * 第一引数 "XXXX" は、日付処理を行うフォーマットの予約語になっています。
	 * ・YMD　　：８文字の4-2-2年月日データ(yyyyMMdd)を扱います。
	 * ・Y2MD　 ：６文字の2-2-2年月日データ(yyMMdd)を扱います。
	 * ・YM　　 ：６文字の4-2年月データ(yyyyMM)を扱います。
	 * ・HMS　　：６文字の2-2-2時分秒データ(HHmmss)を扱います。
	 * ・YMDHMS ：１４文字の4-2-2-2-2-2年月日時分秒データ(yyyyMMddHHmmss)を扱います。
	 * ・EEE　　：曜日をデフォルトロケールで表示します。
	 *
	 * F付きは、フォーマットされた日付を返します。
	 * ・YMDF　 ：１０文字の日付表現(yyyy/MM/dd)を扱います。
	 * ・Y2MDF　：８文字の日付表現(yy/MM/dd)を扱います。
	 * ・YMF　　：７文字の日付表現(yyyy/MM)を扱います。
	 * ・HMSF　 ：８文字の時刻表現(HH:mm:ss)を扱います。
	 * ・YMDHMSF：１９文字の日付表現(yyyy/MM/dd HH:mm:ss)を扱います。
	 * ・MDF　　：５文字の月日表現(MM/dd)を扱います。
	 * ・MDEF　 ：５文字＋曜日の月日表現(MM/dd(EEE))を扱います。
	 * ・MD2F　 ：和暦の月日表現(MM月dd日)を扱います。(5.5.5.2 追加)
	 * ・GYMDF　：和暦の年月日表現(GGGGyyyy年MM月dd日)を扱います。
	 * ・G2YMDF ：和暦の日付表現(Gyyyy/MM/dd)を扱います。
	 * ・GYMF　 ：和暦の年月表現(GGGGyyyy年MM月)を扱います。
	 * ・GYF　　：和暦の年表現(GGGGyyyy)を扱います。
	 *
	 * なお、上記以外のフォーマットを指定する場合は、XXXX部分に直接記述できます。(5.5.5.2 追加)
	 * ただし、基本的には、自由フォーマットは、エラーチェックがない為、使わないでください。
	 *
	 * 第二引数 AA は、基準となる日付を、yyyyMMdd形式で指定します。nullの場合は、現在時刻を使用します。
	 * 指定できる日付は、yyyyMMdd形式を推奨しますが、'/' , '-' , ' ' , ':' を削除して使います。
	 * ６桁の場合は、yyyyMM + 01 とし、８ケタの場合は、yyyyMMdd とし、14ケタ以上の場合は、前半１４文字を
	 * yyyyMMddHHmmss として処理します。それ以外の桁数の場合は、エラーになります。
	 * たとえば、"2012/09/05 16:52:36" のようなフォーマットデータの場合、'/' , '-' , ' ' , ':' を削除して
	 * "20120905165236" に変換後、日付オブジェクトに変換されます。
	 *
	 * 第三引数 BB は、日付についての加減算処理を行うためのコマンドを指定します。
	 * nullの場合は、なにも加減算処理を行いません。
	 * ・SD ：当月の最初の日付にセットします。(当月１日)。CC引数は、-N:N月前、0:当月(=SD)、N:N月後、-1:BSD と同じ、1:ASD と同じ
	 * ・ED ：当月の最後の日付にセットします。(当月月末)。CC引数は、-N:N月前、0:当月(=ED)、N:N月後、-1:BED と同じ、1:AED と同じ
	 * ・SW ：日付処理の週初め(月曜日)にセットします。日付は当日より前に移動します。CC引数は、-N:N週前、0:今週(=SW)、N:N週後
	 * ・EW ：日付処理の週末(日曜日)にセットします。日付は当日より後ろに移動します。CC引数は、-N:N週前、0:今週(=EW)、N:N週後
	 * ・H1 ～ HXXX ：時を指定の分だけ進めます。H1なら１時間後、H24 なら２４時間後(5.5.5.6 (2012/08/31) 追加)
	 * ・D1 ～ DXXX ：日を指定の分だけ進めます。D1なら翌日、D200 なら200日後
	 * ・M1 ～ MXXX ：月を指定の分だけ進めます。M1なら翌月、M6 なら半年後
	 * ・BSD ：(有閑)先月の最初の日付にセットします。(先月１日)(5.5.5.2 追加)。SD -1 と同等
	 * ・BED ：(有閑)先月の最後の日付にセットします。(先月月末)(5.5.5.2 追加)。ED -1 と同等
	 * ・ASD ：(有閑)翌月の最初の日付にセットします。(翌月１日)(5.5.5.2 追加)。SD 1  と同等
	 * ・AED ：(有閑)翌月の最後の日付にセットします。(翌月月末)(5.5.5.2 追加)。ED 1  と同等
	 *
	 * CC 引数は、特別な処理で、BB 引数に対して、加算、減算のための数字を指定できます。(5.7.4.1 (2014/03/14) 追加)
	 * 従来は、BB 引数が、"H" , "D" , "M" の １文字パラメータの場合のみ利用可能でした。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 * @og.rev 5.6.1.1 (2013/02/08) prmB処理を、calendarCalc メソッドへ移動
	 * @og.rev 5.7.4.1 (2014/03/14) CC 引数を拡張
	 * @og.rev 6.4.3.3 (2016/03/04) Map#getOrDefault で対応する。
	 *
	 * @param   key		フォーマットの予約語
	 * @param   prmA	基準となる日付(nullの場合は、処理時刻)
	 * @param   prmB	処理コマンド
	 * @param   intC	加減算処理を行うための数字。0 は、BB引数の従来計算のまま。
	 *
	 * @return   メッセージ情報
	 * @og.rtnNotNull
	 * @see		#getDateFormat( String , String ,String )
	 * @see		#getCalendar( String )						AA 引数 からカレンダオブジェクトを作成します。
	 * @see		#calendarCalc( Calendar , String , int )	BB 引数、CC 引数を元に、日付計算します。
	 */
	public static String getDateFormat( final String key ,final String prmA ,final String prmB ,final int intC ) {

		// prmA が null の場合は、そのまま、現在時刻が使われます。
		final Calendar now = getCalendar( prmA );

		// 5.6.1.1 (2013/02/08) getDateFormat( String ,String ,String ) から分離。
		calendarCalc( now,prmB,intC );		// 5.7.4.1 (2014/03/14) CC 引数を拡張

		// DATE_FORMAT に存在しないフォーマットを指定しても、エラーにしません。
		// ただし、後処理でフォーマットエラーになる可能性は残ります。
		// 6.4.3.3 (2016/03/04) Map#getOrDefault を使用します。
		final String format = DATE_FORMAT.getOrDefault( key,key );		// 後ろの key は、値が null のときの初期値

//		String format = DATE_FORMAT.get( key );
//		if( format == null ) {
//			// DATE_FORMAT に存在しないフォーマットを指定しても、エラーにしません。
//			// ただし、後処理でフォーマットエラーになる可能性は残ります。
//			format = key;	// 5.5.5.2 (2012/08/18) 自由フォーマット指定
//		}

		//5.5.0.2 先頭Gの場合は和暦なのでformatterのLocaleを変更する
		DateFormat formatter = null;
		if( key.indexOf('G') == 0 ){
			formatter = new SimpleDateFormat( format, new Locale("ja","JP","JP"));
		}
		else{
			formatter = new SimpleDateFormat( format,Locale.JAPAN );
		}

		return formatter.format( now.getTime() );
	}

	/**
	 * 開始前設定値、または 終了後設定値の文字列から、オプション文字列を合成します。
	 * 基準となる日付に計算した結果を反映させます。
	 *
	 * CC引数の加減算パラメータは、0 です。
	 *
	 * @og.rev 5.7.4.1 (2014/03/14) CC 引数を拡張するため、旧メソッドを再現しておきます。
	 *
	 * @param   now     基準となる日付（Calendarオブジェクト）
	 * @param   prmB	処理コマンド
	 */
	public static void calendarCalc( final Calendar now,final String prmB ) {
		calendarCalc( now,prmB,0 );
	}

	/**
	 * 開始前設定値、または 終了後設定値の文字列から、オプション文字列を合成します。
	 * 基準となる日付に計算した結果を反映させます。
	 *
	 * prmB は、日付についての加減算処理を行うためのコマンドを指定します。
	 * ・SD ：当月の最初の日付にセットします。(当月１日)。CC引数は、-N:N月前、0:当月(=SD)、N:N月後、-1:BSD と同じ、1:ASD と同じ
	 * ・ED ：当月の最後の日付にセットします。(当月月末)。CC引数は、-N:N月前、0:当月(=ED)、N:N月後、-1:BED と同じ、1:AED と同じ
	 * ・SW ：日付処理の週初め(月曜日)にセットします。日付は当日より前に移動します。CC引数は、-N:N週前、0:今週(=SW)、N:N週後
	 * ・EW ：日付処理の週末(日曜日)にセットします。日付は当日より後ろに移動します。CC引数は、-N:N週前、0:今週(=EW)、N:N週後
	 * ・H1 ～ HXXX ：時を指定の分だけ進めます。H1なら１時間後、H24 なら２４時間後(5.5.5.6 (2012/08/31) 追加)
	 * ・D1 ～ DXXX ：日を指定の分だけ進めます。D1なら翌日、D200 なら200日後
	 * ・M1 ～ MXXX ：月を指定の分だけ進めます。M1なら翌月、M6 なら半年後
	 * ・BSD ：(有閑)先月の最初の日付にセットします。(先月１日)(5.5.5.2 追加)。SD-1 と同等
	 * ・BED ：(有閑)先月の最後の日付にセットします。(先月月末)(5.5.5.2 追加)。ED-1 と同等
	 * ・ASD ：(有閑)翌月の最初の日付にセットします。(翌月１日)(5.5.5.2 追加)。SD1  と同等
	 * ・AED ：(有閑)翌月の最後の日付にセットします。(翌月月末)(5.5.5.2 追加)。ED1  と同等
	 * ・数字：日を指定の分だけ進めます。D1 ～ DXXX の簡略系
	 *
	 * CC 引数は、特別な処理で、BB 引数に対して、加算、減算のための数字を指定できます。(5.7.4.1 (2014/03/14) 追加)
	 * HXXX,DXXX,MXXX 形式に、CC 引数を付けた場合は、XXX にさらに加算されます。
	 * prmB に、数字を使用した場合、(コマンドでない場合)にも、CC 引数は、加算されます。
	 *
	 * @og.rev 5.6.1.1 (2013/02/08) getDateFormat( String ,String ,String ) から分離。
	 * @og.rev 5.7.4.1 (2014/03/14) H1 ～ HXXX ：時を指定の分だけ進める処理が実装されていなかった。
	 * @og.rev 5.7.4.1 (2014/03/14) CC 引数追加
	 *
	 * @param   now     基準となる日付（Calendarオブジェクト）
	 * @param   prmB	処理コマンド
	 * @param   intC	加減算処理を行うための数字。0 は、BB引数の従来計算のまま。
	 */
	public static void calendarCalc( final Calendar now , final String prmB , final int intC ) {

		// 基準は、intC == 0 の場合
		if( prmB != null && prmB.length() > 0 ) {				// 6.1.0.0 (2014/12/26) startsWith→charAt(0)対応
			if( "SD".equals( prmB ) ) {							// (当月１日)
				if( intC != 0 ) { now.add( Calendar.MONTH,intC ); }	// 5.7.4.1 (2014/03/14) CC 引数追加
				now.set( Calendar.DATE,1 );
			}
			else if( "ED".equals( prmB ) ) {					// (当月月末)
				if( intC != 0 ) { now.add( Calendar.MONTH,intC ); }	// 5.7.4.1 (2014/03/14) CC 引数追加
				now.set( Calendar.DATE,now.getActualMaximum( Calendar.DATE ) );
			}
			else if( "BSD".equals( prmB ) ) {					// (先月１日)
				// 5.7.4.1 (2014/03/14) CC 引数追加
				now.add( Calendar.MONTH,intC-1 ); now.set( Calendar.DATE,1 );
			}
			else if( "BED".equals( prmB ) ) {					// (先月月末)
				// 5.7.4.1 (2014/03/14) CC 引数追加
				now.add( Calendar.MONTH,intC-1 ); now.set( Calendar.DATE,now.getActualMaximum( Calendar.DATE ) );
			}
			else if( "ASD".equals( prmB ) ) {					// (翌月１日)
				// 5.7.4.1 (2014/03/14) CC 引数追加
				now.add( Calendar.MONTH,intC+1 ); now.set( Calendar.DATE,1 );
			}
			else if( "AED".equals( prmB ) ) {					// (翌月月末)
				// 5.7.4.1 (2014/03/14) CC 引数追加
				now.add( Calendar.MONTH,intC+1 ); now.set( Calendar.DATE,now.getActualMaximum( Calendar.DATE ) );
			}
			else if( "SW".equals( prmB ) ) {					// 週初め(月曜日)セット
				// 5.7.4.1 (2014/03/14) CC 引数追加
				if( intC != 0 ) { now.add( Calendar.DATE,intC*7 ); }	// まず、基準の日付を週単位で加減算する。

				// 日付型文字列入力データの開始日を月曜日にセットします。
				// SUNDAY=1 , MONDAY=2 になります。月曜日との差だけ、前に戻します。
				// 指定日が日曜日の場合は、月曜日まで戻します。

				final int shu = now.get( Calendar.DAY_OF_WEEK ) - Calendar.MONDAY ;

				if(      shu > 0 ) { now.add( Calendar.DATE, -shu ); }
				else if( shu < 0 ) { now.add( Calendar.DATE, -6 );   }
			}
			else if( "EW".equals( prmB ) ) {					// 週末(日曜日)にセット
				// 5.7.4.1 (2014/03/14) CC 引数追加
				if( intC != 0 ) { now.add( Calendar.DATE,intC*7 ); }	// まず、基準の日付を週単位で加減算する。

				// 日付型文字列入力データの終了日を日曜日にセットします。
				// SUNDAY=1 , MONDAY=2 になります。日曜日になるように、先に進めます。
				final int shu = now.get( Calendar.DAY_OF_WEEK ) ;
				if( shu != Calendar.SUNDAY ) { now.add( Calendar.DATE, 8-shu ); }
			}
	 		// 5.7.4.1 (2014/03/14) H1 ～ HXXX ：時を指定の分だけ進める処理が実装されていなかった。
			else if( prmB.charAt(0) == 'H' ) {			// 6.1.0.0 (2014/12/26) refactoring
				int hour = intC ;
				if( prmB.length() > 1 ) { hour += Integer.parseInt( prmB.substring( 1 ) ); }
				now.add( Calendar.HOUR_OF_DAY , hour );
			}
			else if( prmB.charAt(0) == 'D' ) {			// 6.1.0.0 (2014/12/26) refactoring
				int day = intC ;
				if( prmB.length() > 1 ) { day += Integer.parseInt( prmB.substring( 1 ) ); }
				now.add( Calendar.DATE, day );
			}
			else if( prmB.charAt(0) == 'M' ) {			// 6.1.0.0 (2014/12/26) refactoring
				int month = intC ;
				if( prmB.length() > 1 ) { month += Integer.parseInt( prmB.substring( 1 ) ); }
				now.add( Calendar.MONTH , month );
			}
			else {
				// 上記のパターン以外は、数字（加減算する日数）なので、変換できなければ、フォーマットエラー
				try {
					final int day = Integer.parseInt( prmB ) + intC ;	// 5.7.4.1 (2014/03/14) CC 引数追加
					now.add( Calendar.DATE, day );
				}
				catch( final NumberFormatException ex ) {
					final String errMsg = "日付変数パラメータに、不正な値が指定されました。以下の中から指定しなおしてください。"
								+ "指定可能：[SD,ED,BSD,BED,ASD,AED,SW,EW,H1～HXXX,D1～DXXX,M1～MXXX]"
								+ " prmB=[" + prmB + "]" ;
					throw new OgRuntimeException( errMsg,ex );
				}
			}
		}
	}

	/**
	 * 指定の引数の日付け文字列より、カレンダオブジェクトを作成します。
	 * 引数は、数字以外の文字を削除した状態に変換後、処理に回します。
	 * 不要な文字を削除した状態で、８文字以上になるように指定してください。
	 * 例外的に、６文字の場合は、yyyyMM01 とみなして、"01" 文字列を付与します。
	 * 引数に null を指定すると、現在時刻のカレンダを返します。
	 * それ以外のデータで、８ケタ以下の場合は、RuntimeException が発生します。
	 * ８ケタ以上１４ケタ未満の場合は、８ケタ分を、年月日に分離したカレンダ
	 * オブジェクトを作成します。１４ケタ以上で初めて、時分秒を含むカレンダ
	 * を作成します。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 * @og.rev 5.5.8.2 (2012/11/09) value の判定に、null と ゼロ文字列を判定する。
	 *
	 * @param value 日付け文字列
	 *
	 * @return	カレンダオブジェクト(引数がnullの場合は、現在時刻)
	 * @og.rtnNotNull
	 */
	public static Calendar getCalendar( final String value ) {
		final Calendar cal = Calendar.getInstance();

		if( value == null || value.isEmpty() ) { return cal; }		// 5.5.8.2 (2012/11/09) null と ゼロ文字列を判定する。

		// 日付表記に不要な文字を削除します。
		String dateStr = parseNumber( value ) ;

		if( dateStr.length() == 6 ) { dateStr = dateStr + "01"; }	// yyyyMM01 形式に無理やり合わせる。
		else if( dateStr.length() < 8 ) {
			final String errMsg = "日付指定パラメータに、不正な値が指定されました。value=[" + value + "]" ;
			throw new OgRuntimeException( errMsg );
		}

		cal.clear();	// 日付文字列が存在するので、カレンダをリセット

		final int year   = Integer.parseInt( dateStr.substring( 0,4 ) );
		final int month  = Integer.parseInt( dateStr.substring( 4,6 ) ) - 1;
		final int date   = Integer.parseInt( dateStr.substring( 6,8 ) );

		// 6.3.9.0 (2015/11/06) Use one line for each declaration, it enhances code readability.(PMD)
//		int hour=0, minute=0, second=0;
		int hour  =0;
		int minute=0;
		int second=0;
		if( dateStr.length() >= 14 ) {
			hour   = Integer.parseInt( dateStr.substring( 8,10 ) );
			minute = Integer.parseInt( dateStr.substring( 10,12 ) );
			second = Integer.parseInt( dateStr.substring( 12,14 ) );
		}

		cal.set( year,month,date,hour,minute,second );

		return cal;
	}

	/**
	 * 指定の引数の日付け文字列(yyyyMMdd)より、日付を加算して返します。
	 * マイナスを与えると、減算します。
	 * 日付以上の精度の文字列を渡しても、日付のみの計算となります。
	 * 結果は、引数の日付フォーマットとは全く別で、yyyyMMdd の８文字形式になります。
	 * 引数に null を渡すと、実行時の日付をベースとして処理します。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 *
	 * @param baseDate 日付け文字列(yyyyMMdd)
	 * @param plus     加算する日数(過去にするにはマイナス値を指定する)
	 *
	 * @return	結果の日付(yyyyMMdd)
	 * @og.rtnNotNull
	 */
	public static String getDatePlus( final String baseDate,final int plus ) {
		final Calendar cal = getCalendar( baseDate );
		cal.add( Calendar.DATE,plus );

//		return getDate( cal.getTimeInMillis() , "yyyyMMdd" );
		return DateSet.getDate( cal.getTimeInMillis() , "yyyyMMdd" );
	}

	/**
	 * 現在の月に、指定の月数をプラスした日付文字列を返します。
	 * 日付文字列のフォーマットは、"yyyyMM" です。
	 * 指定する月数にマイナスを指定すると、減算できます。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 *
	 * @param baseDate 日付け文字列(yyyyMM)
	 * @param plus     加算する月数(過去にするにはマイナス値を指定する)
	 *
	 * @return	指定の月数をプラスした日付文字列(yyyyMM)
	 * @og.rtnNotNull
	 */
	public static String getMonthPlus( final String baseDate,final int plus ) {
		final Calendar cal = getCalendar( baseDate );
		cal.set( Calendar.DATE, 1 );		// 当月の 1 日に設定
		cal.add( Calendar.MONTH , plus );

//		return getDate( cal.getTimeInMillis() , "yyyyMM" );
		return DateSet.getDate( cal.getTimeInMillis() , "yyyyMM" );
	}

	/**
	 * 指定の引数の日付け文字列(yyyyMMdd、yyyyMMddHHmmss)に、日付を加算して返します。
	 * マイナスを与えると、減算します。
	 *
	 * 指定する日付には、単位を付与することが可能です。
	 * 単位は、yyyyMMddHHmmss 形式の１文字を指定します。大文字、小文字も識別します。
	 * plus="5M" とすれば、５か月、plus="5d"  とすれば、５日 追加します。
	 * plus に単位を付けない場合は、tani に指定の単位を使います。
	 * plus そのものが、null か、isEmpty の場合は、加算は、1 になります。
	 *
	 * baseDate 文字列を日付文字列に変換後、Calendar で計算し、結果を、format 形式に変換します。
	 * 引数に null を渡すと、実行時の日付をベースとして処理します。
	 *
	 * @og.rev 5.6.1.0 (2013/02/01) 新規作成
	 *
	 * @param baseDate 日付け文字列(yyyyMMdd、yyyyMMddHHmmss 形式の日付文字列)
	 * @param plus     加算する日数(日付単位を含む。単位は、y,M,d,H,m,s の文字で、大文字小文字の区別があります)
	 * @param defTani  日付単位が未指定の場合の初期単位('y','M','d','H','m','s' のどれか)
	 * @param format   返す日付文字列のフォーマット(yyyyMMdd、yyyyMMddHHmmss)
	 *
	 * @return	結果の日付(yyyyMMdd)
	 * @throws	NumberFormatException 加算する日数の単位が('y','M','d','H','m','s')以外の場合。
	 * @og.rtnNotNull
	 */
	public static String getDatePlus( final String baseDate,final String plus,final int defTani,final String format ) {

		int addSu = 1;				// 初期値(plus が null や Empty の場合は、＋１となる)
		int tani  = defTani;

		if( plus != null && !plus.isEmpty() ) {
			boolean flag = true;	// 日付単位を持っているかどうか。持っている場合は、true
			final char ch = plus.charAt( plus.length()-1 );		// 最後の一文字を取得(単位か、数字本体)
			switch( ch ) {
				case 'y' : tani = Calendar.YEAR;		break ;
				case 'M' : tani = Calendar.MONTH;		break ;
				case 'd' : tani = Calendar.DATE;		break ;
				case 'H' : tani = Calendar.HOUR_OF_DAY;	break ;
				case 'm' : tani = Calendar.MINUTE;		break ;
				case 's' : tani = Calendar.SECOND;		break ;
				default	 : flag = false;	break ;		// 日付単位を持っていない。
			}
			if( flag ) {
				addSu = Integer.parseInt( plus.substring( 0,plus.length()-1 ) );	// 日付単位 あり
			}
			else {
				addSu = Integer.parseInt( plus ) ;									// 日付単位 なし
			}
		}

		final Calendar cal = getCalendar( baseDate );
		cal.add( tani,addSu );

//		return getDate( cal.getTimeInMillis() , format );
		return DateSet.getDate( cal.getTimeInMillis() , format );
	}
}
