    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.util;
    017
    018import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    019import java.util.Calendar;
    020import java.util.Locale;
    021import java.util.Map;
    022import java.util.HashMap;
    023import java.text.DateFormat;
    024import java.text.SimpleDateFormat;
    025import java.text.ParseException;
    026import java.util.List;                                                                          // 7.0.5.0 (2019/09/09)
    027import java.util.ArrayList;                                                                     // 7.0.5.0 (2019/09/09)
    028
    029import org.opengion.fukurou.system.DateSet;                                                             // 6.4.2.0 (2016/01/29)
    030import static org.opengion.fukurou.system.HybsConst.BUFFER_SMALL;               // 6.1.0.0 (2014/12/26) refactoring
    031
    032/**
    033 * HybsDateUtil.java は、共通的に使用される Date,Calender関連メソッドを集約した、staticメソッドのみで構成されるクラスです。
    034 *
    035 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    036 *
    037 * @og.group ユーティリティ
    038 *
    039 * @version  5.5
    040 * @author       Kazuhiko Hasegawa
    041 * @since    JDK7.0,
    042 */
    043public final class HybsDateUtil {
    044
    045        /** 各種フォーマットを簡易的に表した文字列 */
    046        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
    047        private static final Map<String,String> DATE_FORMAT = new HashMap<>();
    048        static {
    049                DATE_FORMAT.put( "Y4"           ,"yyyy"                                 );      // 6.9.2.1 (2018/03/12)
    050                DATE_FORMAT.put( "YMD"          ,"yyyyMMdd"                             );
    051                DATE_FORMAT.put( "Y2MD"         ,"yyMMdd"                               );
    052                DATE_FORMAT.put( "YM"           ,"yyyyMM"                               );
    053                DATE_FORMAT.put( "MD"           ,"MMdd"                                 );      // 5.5.5.2 (2012/08/18)
    054                DATE_FORMAT.put( "HMS"          ,"HHmmss"                               );
    055                DATE_FORMAT.put( "HM"           ,"HHmm"                                 );      // 6.7.4.1 (2017/02/17)
    056                DATE_FORMAT.put( "YMDHMS"       ,"yyyyMMddHHmmss"               );
    057                DATE_FORMAT.put( "YMDHM"        ,"yyyyMMddHHmm"                 );      // 7.0.5.0 (2019/09/16)
    058                DATE_FORMAT.put( "YMDH"         ,"yyyyMMddHH"                   );      // 7.0.5.0 (2019/09/16)
    059                DATE_FORMAT.put( "EEE"          ,"EEE"                                  );
    060                DATE_FORMAT.put( "YMDF"         ,"yyyy/MM/dd"                   );
    061                DATE_FORMAT.put( "Y2MDF"        ,"yy/MM/dd"                     );
    062                DATE_FORMAT.put( "YMF"          ,"yyyy/MM"                              );
    063                DATE_FORMAT.put( "HMSF"         ,"HH:mm:ss"                     );
    064                DATE_FORMAT.put( "HMF"          ,"HH:mm"                                );      // 6.7.4.1 (2017/02/17)
    065                DATE_FORMAT.put( "YMDHMSF"      ,"yyyy/MM/dd HH:mm:ss"  );
    066                DATE_FORMAT.put( "YMDHMF"       ,"yyyy/MM/dd HH:mm"             );      // 7.0.5.0 (2019/09/16)
    067                DATE_FORMAT.put( "MDF"          ,"MM/dd"                                ); // 5.5.0.2 (2012/03/09)
    068                DATE_FORMAT.put( "MDEF"         ,"MM/dd(EEE)"                   ); // 5.5.0.2 (2012/03/09) 和暦
    069                DATE_FORMAT.put( "MDHMF"        ,"MM/dd HH:mm"                  ); // 7.0.0.1 (2018/10/09)
    070                DATE_FORMAT.put( "MD2F"         ,"MM月dd日"                               ); // 5.5.5.2 (2012/08/18) 漢字
    071                DATE_FORMAT.put( "HM2F"         ,"HH時mm分"                               ); // 7.0.0.1 (2018/10/09) 漢字
    072                DATE_FORMAT.put( "MDHM2F"       ,"MM月dd日 HH時mm分"        ); // 7.0.0.1 (2018/10/09) 漢字
    073                DATE_FORMAT.put( "GYMDF"        ,"GGGGyyyy年MM月dd日"      ); // 5.5.0.2 (2012/03/09) 和暦
    074                DATE_FORMAT.put( "G2YMDF"       ,"Gyyyy/MM/dd"                  ); // 5.5.0.2 (2012/03/09) 和暦
    075                DATE_FORMAT.put( "GYMF"         ,"GGGGyyyy年MM月"         ); // 5.5.0.2 (2012/03/09) 和暦
    076                DATE_FORMAT.put( "GYF"          ,"GGGGyyyy"                     ); // 5.5.0.2 (2012/03/09) 和暦
    077        }
    078
    079        private static final int DD = 1000 * 60 * 60 * 24 ;             // ミリ秒 → 日
    080        private static final int HH = 1000 * 60 * 60 ;                  // ミリ秒 → 時
    081        private static final int MM = 1000 * 60 ;                               // ミリ秒 → 分
    082        private static final int SS = 1000 ;                                    // ミリ秒 → 秒
    083
    084        /**
    085         *      デフォルトコンストラクターをprivateにして、
    086         *      オブジェクトの生成をさせないようにする。
    087         *
    088         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    089         *
    090         */
    091        private HybsDateUtil() {}
    092
    093        /**
    094         * 指定の文字列から、以下の文字を削除した文字列を返します。
    095         * '/' , '-' , ' ' , ':' の数字以外の文字を含むフォーマットされた
    096         * 日付文字列を、日付データだけに変換する場合に利用することを想定しています。
    097         * よって、マイナス記号や、小数点、コンマなども削除されます。
    098         * このメソッドでは、日付としての整合性や桁チェックは行いませんが、
    099         * 桁数は、6桁、8桁、14桁のどれかに、合わせます。
    100         * 「yyyy/MM/dd HH:mm:ss」 形式を基準としますが、「yyyy/M」「yyyy/M/d」「yy/M/d」「M/d」
    101         * 「HH:mm:ss」「H:m」形式にも、対応します。
    102         * "/" が、"-" に変更されているケースも対応可能ですが、月/年 形式や、英語、日本語の
    103         * 月表示には未対応です。
    104         *
    105         * 引数が、null の場合は、ゼロ文字列に、変換します。
    106         *
    107         * ※ 6.0.2.5 (2014/10/31) 桁数チェックだけは行います。
    108         *   桁数は、6桁、8桁、14桁のどれかに、合わせます。
    109         *
    110         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    111         * @og.rev 5.5.8.3 (2012/11/17) 数字のみ返す仕様だったが、対象以外の文字入力はそのまま返すよう変更
    112         * @og.rev 6.0.2.5 (2014/10/31) 簡易的な桁数チェックだけは行います。
    113         * @og.rev 6.2.3.0 (2015/05/01) 内部処理を大幅に変更します。
    114         *
    115         * @param       value 任意の文字列（例：2001/04/17 15:48:22）
    116         *
    117         * @return      数字だけで構成される文字列（例：20010417154822）(nullはゼロ文字列を返します)
    118         * @og.rtnNotNull
    119         */
    120        public static String parseNumber( final String value ) {
    121                if( value == null || value.isEmpty() ) { return ""; }
    122
    123                // 年 や、年月日 が省略された場合は、実行日をセットする。
    124                final String today = DateSet.getDate( "yyyyMMdd" );
    125
    126                String val = value.trim();
    127                val = val.replaceAll( "-" , "/" );                      // yyyy-MM-dd 形式を、yyyy/MM/dd 形式にする。
    128
    129                final int ad = val.indexOf( ' ' ) ;
    130                String ymd = val  ;
    131                String hms = null ;
    132
    133                if( ad > 0 ) {                                          // スペースがあれば、年月日 と 時分秒 に別れる。
    134                        ymd = val.substring( 0,ad );
    135                        hms = val.substring( ad+1 );
    136                }
    137                else if( val.indexOf( ':' ) > 0 ) {
    138                        ymd = today;                                    // 年月日 は今日になる。
    139                        hms = val;
    140                }
    141
    142                final StringBuilder buf = new StringBuilder( BUFFER_SMALL );
    143
    144                if( ymd != null ) {
    145                        final String[] ymdSp = ymd.split( "/" );
    146                        switch( ymdSp.length ) {
    147                                case 1 : buf.append( ymdSp[0] );        break;                                  // "/" が存在しない。
    148                                case 2 : if( ymdSp[0].length() < 4 ) {                                          // MM/dd のケース。yy/MM は想定外
    149                                                        buf.append( today.substring( 0,4 ) );                   // yyyy の年を設定
    150                                                 }
    151                                                 buf.append( addZero( ymdSp[0] ) ).append( addZero( ymdSp[1] ) );       break;
    152                                default:  if( ymdSp[0].length() == 2 ) {                                        // yy/MM/dd のケースを想定
    153                                                        buf.append( today.substring( 0,2 ) );                   // yy の年の先頭２桁を設定
    154                                                 }
    155                                                 buf.append( ymdSp[0] )
    156                                                        .append( addZero( ymdSp[1] ) )
    157                                                        .append( addZero( ymdSp[2] ) ); break;
    158                        }
    159                }
    160                if( hms != null ) {
    161                        final String[] hmsSp = hms.split( ":" );                                                // HH:mm:ss
    162                        switch( hmsSp.length ) {
    163                                case 1 : buf.append( hmsSp[0] );                        break;                  // ":" が存在しない。
    164                                case 2 : buf.append( addZero( hmsSp[0] ) )                                      // HH:mm のケース。mm:ss は想定外
    165                                                        .append( addZero( hmsSp[1] ) )
    166                                                        .append( "00" );                                break;
    167                                default: buf.append( addZero( hmsSp[0] ) )                                      // HH:mm:ss のケースを想定
    168                                                        .append( addZero( hmsSp[1] ) )
    169                                                        .append( addZero( hmsSp[2] ) ); break;
    170                        }
    171                }
    172
    173                return buf.toString();
    174        }
    175
    176        /**
    177         * 指定の文字列が、一桁の場合、先頭に 0 を追加します。
    178         *
    179         * これは、3/4 の様な日付を、0304 にする場合のサポートメソッドです。
    180         *
    181         * @og.rev 6.2.3.0 (2015/05/01) 新規追加
    182         *
    183         * @param       val 任意の文字列
    184         *
    185         * @return      一桁の場合、先頭に 0 を追加
    186         */
    187        private static String addZero( final String val ) {
    188                return val.length() == 1 ? "0" + val : val ;
    189        }
    190
    191        /**
    192         * 指定の文字列から、yyyy-mm-dd hh:mm:ss 形式の文字列を作成します。
    193         *
    194         * これは、java.sql.Timestamp オブジェクトを文字列から作成するに当たり、
    195         * Timestamp の文字列形式にしなければならないためです。
    196         * 桁数は、８桁 または、１４桁以外の場合は、変換エラーとします。
    197         *
    198         * @og.rev 5.5.8.5 (2012/11/27) 新規作成
    199         *
    200         * @param       value 任意の文字列（例：20010417 or 20010417154822）
    201         *
    202         * @return      Timestampの文字列形式（例：2001-04-17 00:00:00 or 2001-04-17 15:48:22）
    203         */
    204        public static String parseTimestamp( final String value ) {
    205                if( value == null || value.length() != 8 && value.length() != 14 ) {                            // 6.9.7.0 (2018/05/14) PMD
    206                        final String errMsg = "日付文字列は、８桁 または、１４桁で指定してください。"
    207                                                + " value=[" + value + "]" ;
    208                        throw new OgRuntimeException( errMsg );
    209                }
    210
    211                // 6.0.2.5 (2014/10/31) char を append する。
    212                final StringBuilder buf = new StringBuilder( BUFFER_SMALL )
    213                        .append( value.substring( 0,4 ) ).append( '-' )
    214                        .append( value.substring( 4,6 ) ).append( '-' )
    215                        .append( value.substring( 6,8 ) ).append( ' ' );
    216                if( value.length() == 8 ) {
    217                        buf.append( "00:00:00" );
    218                }
    219                else {
    220                        buf.append( value.substring( 8,10  ) ).append( ':' )
    221                                .append( value.substring( 10,12 ) ).append( ':' )
    222                                .append( value.substring( 12,14 ) );
    223                }
    224
    225                return buf.toString();
    226        }
    227
    228        /**
    229         * 日付文字列の桁数の整合性を取ります。
    230         * これは、内部で、parseNumber(String) 処理により、不要なフォーマット記号を削除します。
    231         * ここでは、基本的には、６文字（yyyyMM）、８文字（yyyyMMdd）、１４文字（yyyyMMddHHmmss）
    232         * の日付文字列を作成することを想定していますが、指定の桁数以外は、エラーになります。
    233         *
    234         * 引数が、null         ⇒ 桁数に無関係に、空文字列を返す。
    235         * 引数の桁数が一致     ⇒ その値を返す。
    236         * 引数の桁数が不一致   ⇒ エラー
    237         * ただし、引数の最大長は、１４ケタに制限しています。
    238         *
    239         * このメソッドでは、日付として成立しているかどうか（99999999など）は判定していません。
    240         *
    241         * @og.rev 5.6.6.0 (2013/07/05) メソッドの内容を移す。
    242         *
    243         * @param       value   任意の日付け文字列
    244         * @param       size    変換したい桁数
    245         *
    246         * @return      数字だけで構成される文字列（例：20010417154822）(nullはゼロ文字列を返します)
    247         * @og.rtnNotNull
    248         */
    249        public static String parseDate( final String value , final int size ) {
    250                return parseDate( value , size , size );                // 最小と最大を同じ値にする。
    251        }
    252
    253        /**
    254         * 日付文字列の桁数の整合性を取ります。
    255         * これは、内部で、parseNumber(String) 処理により、不要なフォーマット記号を削除します。
    256         * ここでは、基本的には、６文字（yyyyMM）、８文字（yyyyMMdd）、１４文字（yyyyMMddHHmmss）
    257         * の日付文字列を作成することを想定していますが、それ以外の桁数でも下記のルールに従って
    258         * 処理されます。
    259         *
    260         * 引数が、null         ⇒ 桁数に無関係に、空文字列を返す。
    261         * 引数の桁数が範囲内   ⇒ 以下の処理を実行する。
    262         * 引数の桁数を同じ     ⇒ そのまま返す。
    263         * 引数の桁数より大きい ⇒ 余をカットして、引数の最大長にそろえる。
    264         * 引数の桁数に足りない ⇒ "20000101000000" の文字列の部分文字列を結合させて、引数の最大長にそろえる。
    265         * ただし、引数の最大長は、１４ケタに制限しています。
    266         *
    267         * このメソッドでは、日付として成立しているかどうか（99999999など）は判定していません。
    268         *
    269         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    270         * @og.rev 5.6.1.1 (2013/02/08) 桁数チェック導入。６桁以下だとエラーにする。
    271         * @og.rev 5.6.6.0 (2013/07/05) 桁数チェックの最小-最大指定
    272         * @og.rev 6.2.3.0 (2015/05/01) len == maxSize のとき、パース文字列ではなく、元の値を返していた。
    273         *
    274         * @param       value   任意の日付け文字列
    275         * @param       minSize 変換したい桁数の最小値
    276         * @param       maxSize 変換したい桁数の最大値
    277         *
    278         * @return      数字だけで構成される文字列（例：20010417154822）(nullはゼロ文字列を返します)
    279         * @og.rtnNotNull
    280         */
    281        public static String parseDate( final String value , final int minSize , final int maxSize ) {
    282                if( value == null ) { return ""; }
    283
    284                // 引数の最大長は、１４ケタに制限しています。
    285                if( maxSize > 14 ) {
    286                        final String errMsg = "日付登録に許可できる最大桁数は、１４ケタです。"
    287                                                + " maxSize=[" + maxSize + "]" ;
    288                        throw new OgRuntimeException( errMsg );
    289                }
    290
    291                final String rtn = parseNumber( value );
    292                final int len = rtn.length() ;
    293
    294                if( len < minSize || len > maxSize ) {
    295                        final String errMsg = "日付文字列は、最小["
    296                                                + minSize + "] から、最大[" + maxSize + "]の範囲で指定してください。"
    297                                                + " value=[" + value + "]" ;
    298                        throw new OgRuntimeException( errMsg );
    299                }
    300
    301                return rtn ;
    302        }
    303
    304        /**
    305         * 日付文字列の厳密な整合性チェックを行います。
    306         * ここで指定できるのは、８文字（yyyyMMdd）、１４文字（yyyyMMddHHmmss）のどちらかの
    307         * 数字だけの日付文字列であり、それが、日付として正しいかどうかのチェックを行います。
    308         * 正しければ、true を、間違っていれば、false を返します。
    309         * ここでは、20120230（２月３０日）などの日付や、20120101235960 なども false になります。
    310         * 引数が、null および、空文字列の場合も、false を返しますので、避けたい場合は、事前に
    311         * 判定しておいてください。
    312         *
    313         * 内部処理としては、DateFormat で、setLenient( false ) を設定することで、
    314         * 日付/時刻解析を厳密に解析するにして、ParseException が発生しないかどうか判定しています。
    315         *
    316         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    317         *
    318         * @param       value  数字だけで構成される日付け文字列
    319         *
    320         * @return      true:日付として正しい場合/false:日付として間違っている場合
    321         */
    322        public static boolean isStrict( final String value ) {
    323                if( value == null || value.length() != 8 && value.length() != 14 ) { return false; }    // 6.9.7.0 (2018/05/14) PMD Useless parentheses.
    324
    325                // 日付の厳密なチェック
    326                final String form = (value.length() == 8) ? "yyyyMMdd" : "yyyyMMddHHmmss" ;
    327                final DateFormat formatter = new SimpleDateFormat( form,Locale.JAPAN );
    328                formatter.setLenient( false );          // 日付/時刻解析を厳密に行う(false=厳密)
    329
    330                boolean flag ;
    331                try {
    332                        formatter.parse( value );
    333                        flag = true;
    334                }
    335                catch( final ParseException ex ) {
    336                        flag = false;
    337                }
    338
    339                return flag;
    340        }
    341
    342        /**
    343         * 日付関係の情報を簡易的に処理します。
    344         *
    345         * 引数に与えるのは、{&#064;DATE.XXXX} の XXXX 文字列になります。
    346         * この "XXXX" 文字列は、"key prmA prmB prmC" 形式を取ることができます。
    347         * 各文字列をスペースで分割して、先頭から変数に割り当てます。
    348         * また、prmA の日付文字判定と、数値変換と、prmC の数値変換も行います。
    349         * ただし、リクエスト変数の &#064; 文字列変換は、出来ません。
    350         *
    351         * @og.rev 6.9.2.1 (2018/03/12) 新規追加
    352         *
    353         * @param   value       日付引数のﾊﾟﾗﾒｰﾀ
    354         *
    355         * @return   メッセージ情報
    356         * @og.rtnNotNull
    357         * @see         #getDateFormat( String , String ,String , int )
    358         */
    359        public static String getDateFormat( final String value ) {
    360                // {@DATE.XXXX AA BB CC} を分割
    361                final String[] vals = StringUtil.csv2Array( value,' ' );                // ダブルクオート内は保持される。
    362
    363                final String key = vals[0] ;
    364
    365                String prmA = vals.length >= 2 ? vals[1] : null ;
    366                String prmB = vals.length >= 3 ? vals[2] : null ;
    367                String prmC = vals.length >= 4 ? vals[vals.length-1] : null ;   // 互換性。最後の値が、CC引数
    368
    369                // AA 引数のコマンド判定方法（先頭が数字以外）
    370                if( StringUtil.isNotNull( prmA ) ) {
    371                        final char chA = prmA.charAt(0);
    372                        if( chA < '0' || chA > '9' ) {          // 先頭が、数字以外の場合は、コマンドなので、一つずつずらす。
    373                                prmC = prmB;
    374                                prmB = prmA;
    375                                prmA = null;
    376                        }
    377                }
    378
    379                // CC 引数を、"H" , "D" , "M" 以外でも使用できるように拡張します。
    380                int intC = 0;
    381                if( StringUtil.isNotNull( prmC ) ) {
    382                        try {
    383                                intC = Integer.parseInt( prmC );
    384                        }
    385                        catch( final NumberFormatException ex ) {
    386                                final String errMsg = "CC引数が数字ではありません。value=[" + value + "]"
    387                                                                + ex.getMessage() ;
    388                                System.err.println( errMsg );
    389                        }
    390                }
    391
    392                // prmA が null か、isEmpty() の場合は、現在時刻が使用される。
    393                return getDateFormat( key,prmA,prmB,intC );
    394        }
    395
    396        /**
    397         * 日付関係の情報を簡易的に処理します。
    398         *
    399         * 処理コマンドと、CC引数の加減算パラメータを使用しないバージョンです。
    400         *
    401         * @og.rev 6.9.2.1 (2018/03/12) メソッドの引数を簡素化
    402         *
    403         * @param   key         フォーマットの予約語
    404         * @param   prmA        基準となる日付(nullの場合は、処理時刻)
    405         *
    406         * @return   メッセージ情報
    407         * @og.rtnNotNull
    408         * @see         #getDateFormat( String , String ,String , int )
    409         */
    410        public static String getDateFormat( final String key , final String prmA ) {
    411                return getDateFormat( key,prmA,null,0 );
    412        }
    413
    414//      /**
    415//       * 日付関係の情報を簡易的に処理します。
    416//       *
    417//       * CC引数の加減算パラメータは、0 です。
    418//       *
    419//       * @og.rev 5.7.4.1 (2014/03/14) CC 引数を拡張するため、旧メソッドを再現しておきます。
    420//       * @og.rev 6.9.2.1 (2018/03/12) 廃止
    421//       *
    422//       * @param   key         フォーマットの予約語
    423//       * @param   prmA        基準となる日付(nullの場合は、処理時刻)
    424//       * @param   prmB        処理コマンド
    425//       *
    426//       * @return   メッセージ情報
    427//       * @og.rtnNotNull
    428//       * @see         #getDateFormat( String , String ,String , int )
    429//       */
    430//      public static String getDateFormat( final String key ,final String prmA ,final String prmB ) {
    431//              return getDateFormat( key,prmA,prmB,0 );
    432//      }
    433
    434        /**
    435         * 日付関係の情報を簡易的に処理します。
    436         *
    437         * 第一引数(key) "XXXX" は、日付処理を行うフォーマットの予約語になっています。
    438         * ・Y4　　 ：４文字の年データ(yyyy)を扱います。
    439         * ・YMD　　：８文字の4-2-2年月日データ(yyyyMMdd)を扱います。
    440         * ・Y2MD　 ：６文字の2-2-2年月日データ(yyMMdd)を扱います。
    441         * ・YM　　 ：６文字の4-2年月データ(yyyyMM)を扱います。
    442         * ・HMS　　：６文字の2-2-2時分秒データ(HHmmss)を扱います。
    443         * ・HM　　 ：４文字の2-2時分データ(HHmm)を扱います。6.7.4.1 (2017/02/17)
    444         * ・YMDHMS ：１４文字の4-2-2-2-2-2年月日時分秒データ(yyyyMMddHHmmss)を扱います。
    445         * ・EEE　　：曜日をデフォルトロケールで表示します。
    446         *
    447         * F付きは、フォーマットされた日付を返します。
    448         * ・YMDF　 ：１０文字の日付表現(yyyy/MM/dd)を扱います。
    449         * ・Y2MDF　：８文字の日付表現(yy/MM/dd)を扱います。
    450         * ・YMF　　：７文字の日付表現(yyyy/MM)を扱います。
    451         * ・HMSF　 ：８文字の時刻表現(HH:mm:ss)を扱います。
    452         * ・HMF　  ：５文字の時刻表現(HH:mm)を扱います。6.7.4.1 (2017/02/17)
    453         * ・YMDHMSF：１９文字の日付表現(yyyy/MM/dd HH:mm:ss)を扱います。
    454         * ・MDF　　：５文字の月日表現(MM/dd)を扱います。
    455         * ・MDEF　 ：５文字＋曜日の月日表現(MM/dd(EEE))を扱います。
    456         * ・MDHMF　：１１文字の月日時分表現(MM/dd HH:mm)を扱います。 (7.0.0.1 (2018/10/09) 追加)
    457         * ・MD2F　 ：漢字の月日表現(MM月dd日)を扱います。(5.5.5.2 追加)
    458         * ・HM2F　 ：漢字の時分表現(HH時mm分)を扱います。(7.0.0.1 (2018/10/09) 追加)
    459         * ・MDHM2F ：漢字の月日時分表現(MM月dd日 HH時mm分)を扱います。(7.0.0.1 (2018/10/09) 追加)
    460         * ・GYMDF　：和暦の年月日表現(GGGGyyyy年MM月dd日)を扱います。
    461         * ・G2YMDF ：和暦の日付表現(Gyyyy/MM/dd)を扱います。
    462         * ・GYMF　 ：和暦の年月表現(GGGGyyyy年MM月)を扱います。
    463         * ・GYF　　：和暦の年表現(GGGGyyyy)を扱います。
    464         *
    465         * ・DIFF　 ：日付の差分を求めます。(7.0.1.1 (2018/10/22) 追加)
    466         *               AA - BB を求め、CCの数値で単位を指定します。
    467         *
    468         * なお、上記以外のフォーマットを指定する場合は、XXXX部分に直接記述できます。(5.5.5.2 追加)
    469         * ただし、基本的には、自由フォーマットは、エラーチェックがない為、使わないでください。
    470         *
    471         * 第二引数(prmA) AA は、基準となる日付を、yyyyMMdd形式で指定します。nullの場合は、現在時刻を使用します。
    472         * 指定できる日付は、yyyyMMdd形式を推奨しますが、'/' , '-' , ' ' , ':' を削除して使います。
    473         * ６桁の場合は、yyyyMM + 01 とし、８ケタの場合は、yyyyMMdd とし、14ケタ以上の場合は、前半１４文字を
    474         * yyyyMMddHHmmss として処理します。それ以外の桁数の場合は、エラーになります。
    475         * たとえば、"2012/09/05 16:52:36" のようなフォーマットデータの場合、'/' , '-' , ' ' , ':' を削除して
    476         * "20120905165236" に変換後、日付オブジェクトに変換されます。
    477         *
    478         * 第三引数(prmB) BB は、日付についての加減算処理を行うためのコマンドを指定します。
    479         * nullの場合は、なにも加減算処理を行いません。
    480         * ・SY ：当年の最初の日付にセットします。(当年１月１日)。CC引数は、-N:N年前、0:当年(=SY)、N:N年後 6.9.2.1 (2018/03/12)
    481         * ・SD ：当月の最初の日付にセットします。(当月１日)。CC引数は、-N:N月前、0:当月(=SD)、N:N月後、-1:BSD と同じ、1:ASD と同じ
    482         * ・SW ：日付処理の週初め(月曜日)にセットします。日付は当日より前に移動します。CC引数は、-N:N週前、0:今週(=SW)、N:N週後
    483         * ・SH ：指定の最初の時にセットします。(分秒を0000にする)。CC引数は、時の倍数(4と指定すれば、4時間単位に前に戻る) 6.7.4.1 (2017/02/17)
    484         * ・SM ：指定の最初の分にセットします。(秒を00にする)。CC引数は、分の倍数(15と指定すれば、15分単位に前に戻る) 6.7.4.1 (2017/02/17)
    485         * ・SS ：指定の最初の秒にセットします。CC引数は、秒の倍数(15と指定すれば、15秒単位に前に戻る) 6.7.4.1 (2017/02/17)
    486         * ・EY ：当年の最後の日付にセットします。(当年年末)。CC引数は、-N:N年前、0:当年(=EY)、N:N年後 6.9.2.1 (2018/03/12)
    487         * ・ED ：当月の最後の日付にセットします。(当月月末)。CC引数は、-N:N月前、0:当月(=ED)、N:N月後、-1:BED と同じ、1:AED と同じ
    488         * ・EW ：日付処理の週末(日曜日)にセットします。日付は当日より後ろに移動します。CC引数は、-N:N週前、0:今週(=EW)、N:N週後
    489         * ・EH ：指定の次の時にセットします。(分秒を0000にした次の時)。CC引数は、時の倍数(4と指定すれば、4時間単位に前に進む) 6.7.4.1 (2017/02/17)
    490         * ・EM ：指定の次の分にセットします。(秒を00にした次の分)。CC引数は、分の倍数(15と指定すれば、15分単位に前に進む) 6.7.4.1 (2017/02/17)
    491         * ・ES ：指定の次の秒にセットします。CC引数は、秒の倍数(15と指定すれば、15秒単位に前に進む) 6.7.4.1 (2017/02/17)
    492         * ・M1 ～ MXXX ：月を指定の分だけ進めます。M1なら翌月、M6 なら半年後
    493         * ・D1 ～ DXXX ：日を指定の分だけ進めます。D1なら翌日、D200 なら200日後
    494         * ・H1 ～ HXXX ：時を指定の分だけ進めます。H1なら１時間後、H24 なら２４時間後(5.5.5.6 (2012/08/31) 追加)
    495         * ・MI  ：分を指定の分だけ進めます。第四引数(intC) で、時間を指定します。(6.8.4.1 (2017/12/18) 追加)
    496         * ・YMD ：CC 引数のYMD表記の日付を加減算します。6.8.4.1 (2017/12/18) 追加
    497         * ・HM  ：CC 引数のHM表記の時刻を加減算します。6.8.4.1 (2017/12/18) 追加
    498         * ・NO  ：AA 引数がnullの場合、現在時刻ではなく空文字列にします。  7.0.1.3 (2018/11/12) 追加
    499         * ・(有閑)BSD ：先月の最初の日付にセットします。(先月１日)(5.5.5.2 追加)。SD -1 と同等
    500         * ・(有閑)BED ：先月の最後の日付にセットします。(先月月末)(5.5.5.2 追加)。ED -1 と同等
    501         * ・(有閑)ASD ：翌月の最初の日付にセットします。(翌月１日)(5.5.5.2 追加)。SD 1  と同等
    502         * ・(有閑)AED ：翌月の最後の日付にセットします。(翌月月末)(5.5.5.2 追加)。ED 1  と同等
    503         *
    504         * 7.0.1.1 (2018/10/22)
    505         *   DATE.DIFF の場合、BB 引数は、日付ﾃﾞｰﾀになります。AA-BB の関係です。
    506         *
    507         * CC 引数は、特別な処理で、BB 引数に対して、加算、減算のための数字を指定できます。(5.7.4.1 (2014/03/14) 追加)
    508         * 従来は、BB 引数が、"H" , "D" , "M" の １文字パラメータの場合のみ利用可能でした。
    509         * これは、"H15" と指定するのと、"H" "15" と指定するのと同じ意味になります。
    510         * 異なるのは、CC 引数も、(&#064;CC)指定で、リクエストパラメータが使用できます。
    511         * 従来は、文字列として結合された状態でしか、BB 引数を渡せませんでしたが、この、CC 引数の
    512         * 追加で、日付の加減算を、パラメータ指定できるようになります。
    513         * 数字以外の文字が指定されたり、パラメータの解析結果が NULL の場合には、BB引数自体も無視されます。
    514         * 注意点は、各 BB 引数に応じて、数字の意味が異なるという事です。
    515         *
    516         * HXXX,DXXX,MXXX 形式に、CC 引数を付けた場合は、XXX にさらに加算されます。
    517         * prmB に、数字を使用した場合、(コマンドでない場合)にも、CC 引数は、加算されます。
    518         *
    519         * 7.0.1.1 (2018/10/22)
    520         *   DATE.DIFF の場合、CC 引数は、差分の単位を指定するｷｰﾜｰﾄﾞになります。AA-BB の結果を、
    521         *   1:年 2:月 3:日 4:時 5:分 6:秒 に換算 して返します。端数は切り捨てで整数で返します。
    522         *
    523         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    524         * @og.rev 5.6.1.1 (2013/02/08) prmB処理を、calendarCalc メソッドへ移動
    525         * @og.rev 5.7.4.1 (2014/03/14) CC 引数を拡張
    526         * @og.rev 6.4.3.3 (2016/03/04) Map#getOrDefault で対応する。
    527         * @og.rev 7.0.1.1 (2018/10/22) DATE.DIFF 追加
    528         * @og.rev 7.0.1.3 (2018/11/12) BB 引数に NO追加
    529         *
    530         * @param   key         フォーマットの予約語
    531         * @param   prmA        基準となる日付(nullの場合は、処理時刻)
    532         * @param   prmB        処理コマンド
    533         * @param   intC        加減算処理を行うための数字。0 は、BB引数の従来計算のまま。
    534         *
    535         * @return   メッセージ情報
    536         * @og.rtnNotNull
    537         * @see         #getDateFormat( String )
    538         * @see         #getDateFormat( String , String )
    539         * @see         #getCalendar( String )                                          AA 引数 からカレンダオブジェクトを作成します。
    540         * @see         #calendarCalc( Calendar , String , int )        BB 引数、CC 引数を元に、日付計算します。
    541         */
    542        public static String getDateFormat( final String key ,final String prmA ,final String prmB ,final int intC ) {
    543                // 7.0.1.3 (2018/11/12) prmA が null の場合で、prmB が "NO" の場合は、ゼロ文字列を返します。
    544                if( StringUtil.isEmpty( prmA ) && "NO".equalsIgnoreCase( prmB ) ) {
    545                        return "";
    546                }
    547
    548                // prmA が null の場合は、そのまま、現在時刻が使われます。
    549                final Calendar now = getCalendar( prmA );
    550
    551                // 7.0.1.1 (2018/10/22) DATE.DIFF 追加
    552                if( "DIFF".equalsIgnoreCase( key ) ) {
    553                        return calendarDiff( now,prmB,intC );
    554                }
    555
    556                // 5.6.1.1 (2013/02/08) getDateFormat( String ,String ,String ) から分離。
    557                calendarCalc( now,prmB,intC );          // 5.7.4.1 (2014/03/14) CC 引数を拡張
    558
    559                // DATE_FORMAT に存在しないフォーマットを指定しても、エラーにしません。
    560                // ただし、後処理でフォーマットエラーになる可能性は残ります。
    561                // 6.4.3.3 (2016/03/04) Map#getOrDefault を使用します。
    562                final String format = DATE_FORMAT.getOrDefault( key,key );              // 後ろの key は、値が null のときの初期値
    563
    564                //5.5.0.2 先頭Gの場合は和暦なのでformatterのLocaleを変更する
    565                DateFormat formatter = null;
    566                if( key.indexOf('G') == 0 ){
    567                        formatter = new SimpleDateFormat( format, new Locale("ja","JP","JP"));
    568                }
    569                else{
    570                        formatter = new SimpleDateFormat( format,Locale.JAPAN );
    571                }
    572
    573                return formatter.format( now.getTime() );
    574        }
    575
    576        /**
    577         * 開始前設定値、または 終了後設定値の文字列から、オプション文字列を合成します。
    578         * 基準となる日付に計算した結果を反映させます。
    579         *
    580         * CC引数の加減算パラメータは、0 です。
    581         *
    582         * @og.rev 5.7.4.1 (2014/03/14) CC 引数を拡張するため、旧メソッドを再現しておきます。
    583         *
    584         * @param   now     基準となる日付（Calendarオブジェクト）
    585         * @param   prmB        処理コマンド
    586         */
    587        public static void calendarCalc( final Calendar now,final String prmB ) {
    588                calendarCalc( now,prmB,0 );
    589        }
    590
    591        /**
    592         * 開始前設定値、または 終了後設定値の文字列から、オプション文字列を合成します。
    593         * 基準となる日付に計算した結果を反映させます。
    594         *
    595         * prmB は、日付についての加減算処理を行うためのコマンドを指定します。
    596         * ・SY ：当年の最初の日付にセットします。(当年１月１日)。CC引数は、-N:N年前、0:当年(=SY)、N:N年後 6.9.2.1 (2018/03/12)
    597         * ・SD ：当月の最初の日付にセットします。(当月１日)。CC引数は、-N:N月前、0:当月(=SD)、N:N月後、-1:BSD と同じ、1:ASD と同じ
    598         * ・SW ：日付処理の週初め(月曜日)にセットします。日付は当日より前に移動します。CC引数は、-N:N週前、0:今週(=SW)、N:N週後
    599         * ・SH ：指定の最初の時にセットします。(分秒を0000にする)。CC引数は、時の倍数(4と指定すれば、4時間単位に前に戻る) 6.7.4.1 (2017/02/17)
    600         * ・SM ：指定の最初の分にセットします。(秒を00にする)。CC引数は、分の倍数(15と指定すれば、15分単位に前に戻る) 6.7.4.1 (2017/02/17)
    601         * ・SS ：指定の最初の秒にセットします。CC引数は、秒の倍数(15と指定すれば、15秒単位に前に戻る) 6.7.4.1 (2017/02/17)
    602         * ・EY ：当年の最後の日付にセットします。(当年年末)。CC引数は、-N:N年前、0:当年(=EY)、N:N年後 6.9.2.1 (2018/03/12)
    603         * ・ED ：当月の最後の日付にセットします。(当月月末)。CC引数は、-N:N月前、0:当月(=ED)、N:N月後、-1:BED と同じ、1:AED と同じ
    604         * ・EW ：日付処理の週末(日曜日)にセットします。日付は当日より後ろに移動します。CC引数は、-N:N週前、0:今週(=EW)、N:N週後
    605         * ・EH ：指定の次の時にセットします。(分秒を0000にした次の時)。CC引数は、時の倍数(4と指定すれば、4時間単位に前に進む) 6.7.4.1 (2017/02/17)
    606         * ・EM ：指定の次の分にセットします。(秒を00にした次の分)。CC引数は、分の倍数(15と指定すれば、15分単位に前に進む) 6.7.4.1 (2017/02/17)
    607         * ・ES ：指定の次の秒にセットします。CC引数は、秒の倍数(15と指定すれば、15秒単位に前に進む) 6.7.4.1 (2017/02/17)
    608         * ・M1 ～ MXXX ：月を指定の分だけ進めます。M1なら翌月、M6 なら半年後
    609         * ・D1 ～ DXXX ：日を指定の分だけ進めます。D1なら翌日、D200 なら200日後
    610         * ・H1 ～ HXXX ：時を指定の分だけ進めます。H1なら１時間後、H24 なら２４時間後(5.5.5.6 (2012/08/31) 追加)
    611         * ・MI  ：分を指定の分だけ進めます。第四引数(intC) で、時間を指定します。(6.8.4.1 (2017/12/18) 追加)
    612         * ・YMD ：CC 引数のYMD表記の日付を加減算します。6.8.4.1 (2017/12/18) 追加
    613         * ・HM  ：CC 引数のHM表記の時刻を加減算します。6.8.4.1 (2017/12/18) 追加
    614         * ・NO  ：AA 引数がnullの場合、現在時刻ではなく空文字列にします。  7.0.1.3 (2018/11/12) 追加
    615         * ・(有閑)BSD ：先月の最初の日付にセットします。(先月１日)(5.5.5.2 追加)。SD-1 と同等
    616         * ・(有閑)BED ：先月の最後の日付にセットします。(先月月末)(5.5.5.2 追加)。ED-1 と同等
    617         * ・(有閑)ASD ：翌月の最初の日付にセットします。(翌月１日)(5.5.5.2 追加)。SD1  と同等
    618         * ・(有閑)AED ：翌月の最後の日付にセットします。(翌月月末)(5.5.5.2 追加)。ED1  と同等
    619         * ・数字：日を指定の分だけ進めます。D1 ～ DXXX の簡略系
    620         *
    621         * CC 引数は、特別な処理で、BB 引数に対して、加算、減算のための数字を指定できます。(5.7.4.1 (2014/03/14) 追加)
    622         * HXXX,DXXX,MXXX 形式に、CC 引数を付けた場合は、XXX にさらに加算されます。
    623         * prmB に、数字を使用した場合、(コマンドでない場合)にも、CC 引数は、加算されます。
    624         * 6.8.4.1 (2017/12/18) BB 引数に、HM を指定した場合、時分のデータを加算します。この場合、時に関しては、24時間を越える
    625         * 場合は、日付に加算されます。例えば、翌朝の６時を指定する場合、3000 と指定することで、１日＋0600 となります。
    626         *
    627         * @og.rev 5.6.1.1 (2013/02/08) getDateFormat( String ,String ,String ) から分離。
    628         * @og.rev 5.7.4.1 (2014/03/14) H1 ～ HXXX ：時を指定の分だけ進める処理が実装されていなかった。
    629         * @og.rev 5.7.4.1 (2014/03/14) CC 引数追加
    630         * @og.rev 6.7.4.1 (2017/02/17) SH,SM,SS,EH,EM,ES 追加
    631         * @og.rev 6.8.4.1 (2017/12/18) YMD , HM , MI 追加
    632         * @og.rev 6.9.2.1 (2018/03/12) 年関連の機能(Y4,SY,EY)追加
    633         * @og.rev 7.0.1.3 (2018/11/12) BB 引数に NO追加
    634         *
    635         * @param   now     基準となる日付（Calendarオブジェクト）
    636         * @param   prmB        処理コマンド
    637         * @param   intC        加減算処理を行うための数字。0 は、BB引数の従来計算のまま。
    638         */
    639        public static void calendarCalc( final Calendar now , final String prmB , final int intC ) {
    640
    641                // 基準は、intC == 0 の場合
    642                if( prmB != null && prmB.length() > 0 ) {                               // 6.9.2.1 (2018/03/12) 年関連の機能(Y4,SY,EY)追加
    643                        if( "SY".equals( prmB ) ) {                                                     // (当年１月１日)
    644                                if( intC != 0 ) { now.add( Calendar.YEAR,intC ); }
    645                                now.set( Calendar.DAY_OF_YEAR,1 );                              // 現在の年の何日目かで指定します。
    646                        }
    647                        else if( "EY".equals( prmB ) ) {                                        // (当年年末)
    648                                if( intC != 0 ) { now.add( Calendar.YEAR,intC ); }
    649                                now.set( Calendar.MONTH,11 );                                   // 月は、0～11 の値で指定。
    650                                now.set( Calendar.DATE,now.getActualMaximum( Calendar.DATE ) );
    651                        }
    652                        else if( "SD".equals( prmB ) ) {                                                        // (当月１日)
    653                                if( intC != 0 ) { now.add( Calendar.MONTH,intC ); }     // 5.7.4.1 (2014/03/14) CC 引数追加
    654                                now.set( Calendar.DATE,1 );
    655                        }
    656                        else if( "ED".equals( prmB ) ) {                                        // (当月月末)
    657                                if( intC != 0 ) { now.add( Calendar.MONTH,intC ); }     // 5.7.4.1 (2014/03/14) CC 引数追加
    658                                now.set( Calendar.DATE,now.getActualMaximum( Calendar.DATE ) );
    659                        }
    660                        else if( "SH".equals( prmB ) ) {                                        // (時戻し) 6.7.4.1 (2017/02/17) SH,SM,SS,EH,EM,ES 追加
    661                                // 6.9.8.0 (2018/05/28) FindBugs:整数の除算の結果を double または float にキャストしている
    662//                              final int hh = intC == 0 ? 0 : (int)Math.floor( now.get( Calendar.HOUR_OF_DAY ) / intC ) * intC ;       // 切捨て
    663                                final int hh = intC == 0 ? 0 : ( now.get( Calendar.HOUR_OF_DAY ) / intC ) * intC ;      // 切捨て
    664                                now.set( Calendar.HOUR_OF_DAY,hh );
    665                                now.set( Calendar.MINUTE     ,0 );
    666                                now.set( Calendar.SECOND     ,0 );
    667                                now.set( Calendar.MILLISECOND,0 );
    668                        }
    669                        else if( "SM".equals( prmB ) ) {                                        // (分戻し) 6.7.4.1 (2017/02/17) SH,SM,SS,EH,EM,ES 追加
    670                                // 6.9.8.0 (2018/05/28) FindBugs:整数の除算の結果を double または float にキャストしている
    671//                              final int mm = intC == 0 ? 0 : (int)Math.floor( now.get( Calendar.MINUTE ) / intC ) * intC ;    // 切捨て
    672                                final int mm = intC == 0 ? 0 : ( now.get( Calendar.MINUTE ) / intC ) * intC ;   // 切捨て
    673                                now.set( Calendar.MINUTE     ,mm );
    674                                now.set( Calendar.SECOND     ,0 );
    675                                now.set( Calendar.MILLISECOND,0 );
    676                        }
    677                        else if( "SS".equals( prmB ) ) {                                        // (秒戻し) 6.7.4.1 (2017/02/17) SH,SM,SS,EH,EM,ES 追加
    678                                // 6.9.8.0 (2018/05/28) FindBugs:整数の除算の結果を double または float にキャストしている
    679//                              final int ss = intC == 0 ? 0 : (int)Math.floor( now.get( Calendar.SECOND ) / intC ) * intC ;    // 切捨て
    680                                final int ss = intC == 0 ? 0 : ( now.get( Calendar.SECOND ) / intC ) * intC ;   // 切捨て
    681                                now.set( Calendar.SECOND     ,ss );
    682                                now.set( Calendar.MILLISECOND,0 );
    683                        }
    684                        else if( "EH".equals( prmB ) ) {                                        // (時進め) 6.7.4.1 (2017/02/17) SH,SM,SS,EH,EM,ES 追加
    685                                final int hh = intC == 0 ? 0 : (int)Math.ceil( (double)now.get( Calendar.HOUR_OF_DAY ) / intC ) * intC ;        // 切り上げ
    686                                if( hh == 0 || hh >= 24 ) {
    687                                        now.add( Calendar.DATE       ,1 );                      // 日を加算
    688                                        now.set( Calendar.HOUR_OF_DAY,0 );
    689                                }
    690                                else {
    691                                        now.set( Calendar.HOUR_OF_DAY,hh );
    692                                }
    693                                now.set( Calendar.MINUTE     ,0 );
    694                                now.set( Calendar.SECOND     ,0 );
    695                                now.set( Calendar.MILLISECOND,0 );
    696                        }
    697                        else if( "EM".equals( prmB ) ) {                                        // (分進め) 6.7.4.1 (2017/02/17) SH,SM,SS,EH,EM,ES 追加
    698                                final int mm = intC == 0 ? 0 : (int)Math.ceil( (double)now.get( Calendar.MINUTE ) / intC ) * intC ;             // 切切り上げ
    699                                if( mm == 0 || mm >= 60 ) {
    700                                        now.add( Calendar.HOUR_OF_DAY,1 );                      // 時を加算
    701                                        now.set( Calendar.MINUTE     ,0 );
    702                                }
    703                                else {
    704                                        now.set( Calendar.MINUTE,mm );
    705                                }
    706                                now.set( Calendar.SECOND     ,0 );
    707                                now.set( Calendar.MILLISECOND,0 );
    708                        }
    709                        else if( "ES".equals( prmB ) ) {                                        // (秒進め) 6.7.4.1 (2017/02/17) SH,SM,SS,EH,EM,ES 追加
    710                                final int ss = intC == 0 ? 0 : (int)Math.ceil( (double)now.get( Calendar.SECOND ) / intC ) * intC ;     // 切り上げ
    711                                if( ss == 0 || ss >= 60 ) {
    712                                        now.add( Calendar.MINUTE ,1 );                          // 分を加算
    713                                        now.set( Calendar.SECOND ,0 );
    714                                }
    715                                else {
    716                                        now.set( Calendar.SECOND,ss );
    717                                }
    718                                now.set( Calendar.MILLISECOND,0 );
    719                        }
    720                        else if( "BSD".equals( prmB ) ) {                                       // (先月１日)
    721                                // 5.7.4.1 (2014/03/14) CC 引数追加
    722                                now.add( Calendar.MONTH,intC-1 ); now.set( Calendar.DATE,1 );
    723                        }
    724                        else if( "BED".equals( prmB ) ) {                                       // (先月月末)
    725                                // 5.7.4.1 (2014/03/14) CC 引数追加
    726                                now.add( Calendar.MONTH,intC-1 ); now.set( Calendar.DATE,now.getActualMaximum( Calendar.DATE ) );
    727                        }
    728                        else if( "ASD".equals( prmB ) ) {                                       // (翌月１日)
    729                                // 5.7.4.1 (2014/03/14) CC 引数追加
    730                                now.add( Calendar.MONTH,intC+1 ); now.set( Calendar.DATE,1 );
    731                        }
    732                        else if( "AED".equals( prmB ) ) {                                       // (翌月月末)
    733                                // 5.7.4.1 (2014/03/14) CC 引数追加
    734                                now.add( Calendar.MONTH,intC+1 ); now.set( Calendar.DATE,now.getActualMaximum( Calendar.DATE ) );
    735                        }
    736                        else if( "SW".equals( prmB ) ) {                                        // 週初め(月曜日)セット
    737                                // 5.7.4.1 (2014/03/14) CC 引数追加
    738                                if( intC != 0 ) { now.add( Calendar.DATE,intC*7 ); }    // まず、基準の日付を週単位で加減算する。
    739
    740                                // 日付型文字列入力データの開始日を月曜日にセットします。
    741                                // SUNDAY=1 , MONDAY=2 になります。月曜日との差だけ、前に戻します。
    742                                // 指定日が日曜日の場合は、月曜日まで戻します。
    743
    744                                final int shu = now.get( Calendar.DAY_OF_WEEK ) - Calendar.MONDAY ;
    745
    746                                if(      shu > 0 ) { now.add( Calendar.DATE, -shu ); }
    747                                else if( shu < 0 ) { now.add( Calendar.DATE, -6 );   }
    748                        }
    749                        else if( "EW".equals( prmB ) ) {                                        // 週末(日曜日)にセット
    750                                // 5.7.4.1 (2014/03/14) CC 引数追加
    751                                if( intC != 0 ) { now.add( Calendar.DATE,intC*7 ); }    // まず、基準の日付を週単位で加減算する。
    752
    753                                // 日付型文字列入力データの終了日を日曜日にセットします。
    754                                // SUNDAY=1 , MONDAY=2 になります。日曜日になるように、先に進めます。
    755                                final int shu = now.get( Calendar.DAY_OF_WEEK ) ;
    756                                if( shu != Calendar.SUNDAY ) { now.add( Calendar.DATE, 8-shu ); }
    757                        }
    758                        // 6.8.4.1 (2017/12/18) YMD 追加(年月日の繰り上げ表記を加味した加算)
    759                        else if( "YMD".equals( prmB ) ) {
    760                                final int year  = intC / 10000;
    761                                final int month = ( intC / 100 ) % 100;
    762                                final int date  = intC % 100;
    763
    764                                now.add( Calendar.YEAR  , year );
    765                                now.add( Calendar.MONTH , month );
    766                                now.add( Calendar.DATE  , date );
    767                        }
    768                        // 6.8.4.1 (2017/12/18) HM 追加(時分の２４時間表記を加味した加算)
    769                        else if( "HM".equals( prmB ) ) {                        // 注意：prmB.charAt(0) == 'H' より先に判定しておきます。
    770                                final int hour   = intC / 100;
    771                                final int minute = intC % 100;
    772
    773                                now.add( Calendar.HOUR_OF_DAY   , hour );
    774                                now.add( Calendar.MINUTE                , minute );
    775                        }
    776                        // 6.8.4.1 (2017/12/18) HM 分を指定の分だけ進めます。
    777                        else if( "MI".equals( prmB ) ) {                        // 注意：prmB.charAt(0) == 'M' より先に判定しておきます。
    778                                now.add( Calendar.MINUTE                , intC );
    779                        }
    780                        // 7.0.1.3 (2018/11/12) AA 引数がnullの場合、現在時刻ではなく空文字列にします。
    781                        else if( "NO".equals( prmB ) ) {
    782                                ;       // 何もしません。
    783                        }
    784                        // 6.9.2.1 (2018/03/12) Y1 ～ YXXX ：年を指定の分だけ進める処理
    785                        else if( prmB.charAt(0) == 'Y' ) {
    786                                int year = intC ;
    787                                if( prmB.length() > 1 ) { year += Integer.parseInt( prmB.substring( 1 ) ); }
    788                                now.add( Calendar.YEAR , year );
    789                        }
    790                        else if( prmB.charAt(0) == 'H' ) {                      // 6.1.0.0 (2014/12/26) refactoring
    791                                int hour = intC ;
    792                                if( prmB.length() > 1 ) { hour += Integer.parseInt( prmB.substring( 1 ) ); }
    793                                now.add( Calendar.HOUR_OF_DAY , hour );
    794                        }
    795                        else if( prmB.charAt(0) == 'D' ) {                      // 6.1.0.0 (2014/12/26) refactoring
    796                                int day = intC ;
    797                                if( prmB.length() > 1 ) { day += Integer.parseInt( prmB.substring( 1 ) ); }
    798                                now.add( Calendar.DATE, day );
    799                        }
    800                        else if( prmB.charAt(0) == 'M' ) {                      // 6.1.0.0 (2014/12/26) refactoring
    801                                int month = intC ;
    802                                if( prmB.length() > 1 ) { month += Integer.parseInt( prmB.substring( 1 ) ); }
    803                                now.add( Calendar.MONTH , month );
    804                        }
    805                        else {
    806                                // 上記のパターン以外は、数字（加減算する日数）なので、変換できなければ、フォーマットエラー
    807                                try {
    808                                        final int day = Integer.parseInt( prmB ) + intC ;       // 5.7.4.1 (2014/03/14) CC 引数追加
    809                                        now.add( Calendar.DATE, day );
    810                                }
    811                                catch( final NumberFormatException ex ) {
    812                                        final String errMsg = "日付変数パラメータに、不正な値が指定されました。以下の中から指定しなおしてください。"
    813//                                                              + "指定可能：[SD,ED,SW,SH,SM,SS,EW,EH,EM,ES,H1～HXXX,D1～DXXX,M1～MXXX,HMS,BSD,BED,ASD,AED]"
    814                                                                + "指定可能：[SY,SD,SW,SH,SM,SS,EY,ED,EW,EH,EM,ES,M1～MXXX,D1～DXXX,H1～HXXX,MI,YMD,HM,NO]"
    815                                                                + " prmB=[" + prmB + "]" ;
    816                                        throw new OgRuntimeException( errMsg,ex );
    817                                }
    818                        }
    819                }
    820        }
    821
    822        /**
    823         * 日付の差分を求めます。
    824         * timeA - timeB を求め、intCの数値で単位を指定します。
    825         *
    826         * intC は、1:年 2:月 3:日 4:時 5:分 6:秒 に換算した単位での差分になります。
    827         * 端数は出ません。
    828         *
    829         * @og.rev 7.0.1.1 (2018/10/22) DATE.DIFF 追加
    830         * @og.rev 7.2.9.4 (2020/11/20) spotbugs:switch 文の2つの case のために同じコードを使用しているメソッド
    831         *
    832         * @param   timeA   基準となる日付（Calendarオブジェクト）
    833         * @param   prmB        日付ﾃﾞｰﾀになります。AA-BB の関係です。
    834         * @param   intC        差分の単位を指定するｷｰﾜｰﾄﾞ(1:年 2:月 3:日 4:時 5:分 6:秒 に換算)
    835         * @return      指定の時間の差(timeA - timeB マイナスもある)
    836         */
    837        public static String calendarDiff( final Calendar timeA , final String prmB , final int intC ) {
    838                final Calendar timeB = getCalendar( prmB );
    839
    840                final int diff ;
    841                if( intC == 1 ) {                       // 差分年
    842                        diff = timeA.get( Calendar.YEAR ) - timeB.get( Calendar.YEAR );
    843                }
    844                else if( intC == 2 ) {          // 差分月数
    845                        diff = ( timeA.get( Calendar.YEAR  ) - timeB.get( Calendar.YEAR  ) ) * 12
    846                                +  ( timeA.get( Calendar.MONTH ) - timeB.get( Calendar.MONTH ) ) ;
    847
    848        //              // 月の計算ﾛｼﾞｯｸ( http://javatechnology.net/java/date-diff-month/ )
    849        //              // マイナス計算も必要なので、今回は不採用
    850        //              timeA.set( Calendar.DATE, 1 );          // 端数を無視するため、1日にセットします。
    851        //              timeB.set( Calendar.DATE, 1 );          // 同上
    852
    853        //              int count = 0;
    854        //              while( timeA.before( timeB ) ) {
    855        //                      timeA.add( Calendar.MONTH, 1 );
    856        //                      count++;
    857        //              }
    858        //              diff = count;
    859                }
    860                else {
    861                        final long diffSec = timeA.getTimeInMillis() - timeB.getTimeInMillis() ;        // 時間の差(ミリ秒)
    862
    863                        switch( intC ) {
    864        //                      case 3:  diff = (int)( diffSec / DD );  break;          // 日単位                  // 7.2.9.4 (2020/11/20)
    865                                case 4:  diff = (int)( diffSec / HH );  break;          // 時単位
    866                                case 5:  diff = (int)( diffSec / MM );  break;          // 分単位
    867                                case 6:  diff = (int)( diffSec / SS );  break;          // 秒単位
    868                                default: diff = (int)( diffSec / DD );  break;          // (初期値)日単位
    869                        }
    870                }
    871
    872                return Integer.toString( diff ) ;
    873        }
    874
    875        /**
    876         * 指定の引数の日付け文字列より、カレンダオブジェクトを作成します。
    877         * 引数は、数字以外の文字を削除した状態に変換後、処理に回します。
    878         * 不要な文字を削除した状態で、８文字以上になるように指定してください。
    879         * 例外的に、６文字の場合は、yyyyMM01 とみなして、"01" 文字列を付与します。
    880         * 引数に null を指定すると、現在時刻のカレンダを返します。
    881         * それ以外のデータで、８ケタ以下の場合は、RuntimeException が発生します。
    882         * ８ケタ以上１４ケタ未満の場合は、８ケタ分を、年月日に分離したカレンダ
    883         * オブジェクトを作成します。１４ケタ以上で初めて、時分秒を含むカレンダ
    884         * を作成します。
    885         *
    886         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    887         * @og.rev 5.5.8.2 (2012/11/09) value の判定に、null と ゼロ文字列を判定する。
    888         *
    889         * @param value 日付け文字列
    890         *
    891         * @return      カレンダオブジェクト(引数がnullの場合は、現在時刻)
    892         * @og.rtnNotNull
    893         */
    894        public static Calendar getCalendar( final String value ) {
    895                final Calendar cal = Calendar.getInstance();
    896
    897                if( value == null || value.isEmpty() ) { return cal; }          // 5.5.8.2 (2012/11/09) null と ゼロ文字列を判定する。
    898
    899                // 日付表記に不要な文字を削除します。
    900                String dateStr = parseNumber( value ) ;
    901
    902                if( dateStr.length() == 6 ) { dateStr = dateStr + "01"; }       // yyyyMM01 形式に無理やり合わせる。
    903                else if( dateStr.length() < 8 ) {
    904                        final String errMsg = "日付指定パラメータに、不正な値が指定されました。value=[" + value + "]" ;
    905                        throw new OgRuntimeException( errMsg );
    906                }
    907
    908                cal.clear();    // 日付文字列が存在するので、カレンダをリセット
    909
    910                final int year   = Integer.parseInt( dateStr.substring( 0,4 ) );
    911                final int month  = Integer.parseInt( dateStr.substring( 4,6 ) ) - 1;
    912                final int date   = Integer.parseInt( dateStr.substring( 6,8 ) );
    913
    914                // 6.3.9.0 (2015/11/06) Use one line for each declaration, it enhances code readability.(PMD)
    915                int hour  =0;
    916                int minute=0;
    917                int second=0;
    918                if( dateStr.length() >= 14 ) {
    919                        hour   = Integer.parseInt( dateStr.substring( 8,10 ) );
    920                        minute = Integer.parseInt( dateStr.substring( 10,12 ) );
    921                        second = Integer.parseInt( dateStr.substring( 12,14 ) );
    922                }
    923
    924                cal.set( year,month,date,hour,minute,second );
    925
    926                return cal;
    927        }
    928
    929        /**
    930         * 指定の引数の日付け文字列(yyyyMMdd)より、日付を加算して返します。
    931         * マイナスを与えると、減算します。
    932         * 日付以上の精度の文字列を渡しても、日付のみの計算となります。
    933         * 結果は、引数の日付フォーマットとは全く別で、yyyyMMdd の８文字形式になります。
    934         * 引数に null を渡すと、実行時の日付をベースとして処理します。
    935         *
    936         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    937         *
    938         * @param baseDate 日付け文字列(yyyyMMdd)
    939         * @param plus     加算する日数(過去にするにはマイナス値を指定する)
    940         *
    941         * @return      結果の日付(yyyyMMdd)
    942         * @og.rtnNotNull
    943         */
    944        public static String getDatePlus( final String baseDate,final int plus ) {
    945                final Calendar cal = getCalendar( baseDate );
    946                cal.add( Calendar.DATE,plus );
    947
    948                return DateSet.getDate( cal.getTimeInMillis() , "yyyyMMdd" );
    949        }
    950
    951        /**
    952         * 現在の月に、指定の月数をプラスした日付文字列を返します。
    953         * 日付文字列のフォーマットは、"yyyyMM" です。
    954         * 指定する月数にマイナスを指定すると、減算できます。
    955         *
    956         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
    957         *
    958         * @param baseDate 日付け文字列(yyyyMM)
    959         * @param plus     加算する月数(過去にするにはマイナス値を指定する)
    960         *
    961         * @return      指定の月数をプラスした日付文字列(yyyyMM)
    962         * @og.rtnNotNull
    963         */
    964        public static String getMonthPlus( final String baseDate,final int plus ) {
    965                final Calendar cal = getCalendar( baseDate );
    966                cal.set( Calendar.DATE, 1 );            // 当月の 1 日に設定
    967                cal.add( Calendar.MONTH , plus );
    968
    969                return DateSet.getDate( cal.getTimeInMillis() , "yyyyMM" );
    970        }
    971
    972        /**
    973         * 指定の引数の日付け文字列(yyyyMMdd、yyyyMMddHHmmss)に、日付を加算して返します。
    974         * マイナスを与えると、減算します。
    975         *
    976         * 指定する日付には、単位を付与することが可能です。
    977         * 単位は、yyyyMMddHHmmss 形式の１文字を指定します。大文字、小文字も識別します。
    978         * plus="5M" とすれば、５か月、plus="5d"  とすれば、５日 追加します。
    979         * plus に単位を付けない場合は、tani に指定の単位を使います。
    980         * plus そのものが、null か、isEmpty の場合は、加算は、1 になります。
    981         *
    982         * baseDate 文字列を日付文字列に変換後、Calendar で計算し、結果を、format 形式に変換します。
    983         * 引数に null を渡すと、実行時の日付をベースとして処理します。
    984         *
    985         * @og.rev 5.6.1.0 (2013/02/01) 新規作成
    986         *
    987         * @param baseDate 日付け文字列(yyyyMMdd、yyyyMMddHHmmss 形式の日付文字列)
    988         * @param plus     加算する日数(日付単位を含む。単位は、y,M,d,H,m,s の文字で、大文字小文字の区別があります)
    989         * @param defTani  日付単位が未指定の場合の初期単位('y','M','d','H','m','s' のどれか)
    990         * @param format   返す日付文字列のフォーマット(yyyyMMdd、yyyyMMddHHmmss)
    991         *
    992         * @return      結果の日付(yyyyMMdd)
    993         * @throws      NumberFormatException 加算する日数の単位が('y','M','d','H','m','s')以外の場合。
    994         * @og.rtnNotNull
    995         */
    996        public static String getDatePlus( final String baseDate,final String plus,final int defTani,final String format ) {
    997
    998                int addSu = 1;                          // 初期値(plus が null や Empty の場合は、＋１となる)
    999                int tani  = defTani;
    1000
    1001                if( plus != null && !plus.isEmpty() ) {
    1002                        boolean flag = true;    // 日付単位を持っているかどうか。持っている場合は、true
    1003                        final char ch = plus.charAt( plus.length()-1 );         // 最後の一文字を取得(単位か、数字本体)
    1004                        switch( ch ) {
    1005                                case 'y' : tani = Calendar.YEAR;                break ;
    1006                                case 'M' : tani = Calendar.MONTH;               break ;
    1007                                case 'd' : tani = Calendar.DATE;                break ;
    1008                                case 'H' : tani = Calendar.HOUR_OF_DAY; break ;
    1009                                case 'm' : tani = Calendar.MINUTE;              break ;
    1010                                case 's' : tani = Calendar.SECOND;              break ;
    1011                                default  : flag = false;        break ;         // 日付単位を持っていない。
    1012                        }
    1013                        if( flag ) {
    1014                                addSu = Integer.parseInt( plus.substring( 0,plus.length()-1 ) );        // 日付単位 あり
    1015                        }
    1016                        else {
    1017                                addSu = Integer.parseInt( plus ) ;                                                                      // 日付単位 なし
    1018                        }
    1019                }
    1020
    1021                final Calendar cal = getCalendar( baseDate );
    1022                cal.add( tani,addSu );
    1023
    1024                return DateSet.getDate( cal.getTimeInMillis() , format );
    1025        }
    1026
    1027        /**
    1028         * 指定の日付文字列を指定の形式の文字列に変換します。
    1029         * 入力の日付文字列は、"yyyyMMddHHmmss" または、 "yyyyMMdd" です。
    1030         * 記号が入っている場合でも、一応処理します。
    1031         * 指定のフォーマットも、同様に、yyyyMMddHHmmss 形式で指定します。
    1032         *
    1033         * @og.rev 7.0.1.2 (2018/11/04) 新規登録
    1034         *
    1035         * @param ymd           日付け文字列(yyyyMM)
    1036         * @param format        加算する月数(過去にするにはマイナス値を指定する)
    1037         *
    1038         * @return      指定の日付文字列を指定の形式の文字列に変換
    1039         * @og.rtnNotNull
    1040         */
    1041        public static String toYmd( final String ymd,final String format ) {
    1042                final Calendar cal = getCalendar( ymd );
    1043
    1044                return DateSet.getDate( cal.getTimeInMillis() , format );
    1045        }
    1046
    1047        /**
    1048         * 指定の年月文字列のFrom-Toを、文字列の配列で返します。
    1049         * "yyyyMM" 形式の範囲内の文字列配列を作成します。
    1050         *
    1051         * ※ endがnullの場合は、beginそのものを配列にセットして返します。
    1052         *    つまり、1件のみの配列になります。
    1053         * ※ end == beginの場合は、配列は、ゼロ個の配列になります。
    1054         *
    1055         * @og.rev 7.0.5.0 (2019/09/09) 新規追加
    1056         *
    1057         * @param begin         開始の年月(含む)
    1058         * @param end           終了の年月(含まない)
    1059         * @param step          加算する月数
    1060         *
    1061         * @return      指定の年月文字列のFrom-Toを、文字列の配列で返す
    1062         * @og.rtnNotNull
    1063         */
    1064        public static String[] stepYM( final String begin,final String end,final int step ) {
    1065                final List<String> list = new ArrayList<>();
    1066
    1067                if( StringUtil.isNull( end ) ) {
    1068                        list.add( begin );
    1069                }
    1070                else {
    1071                        final Calendar timeA = getCalendar( begin );
    1072                        final Calendar timeB = getCalendar( end );
    1073
    1074                        while( timeA.before( timeB ) ) {
    1075                                list.add( DateSet.getDate( timeA.getTimeInMillis() , "yyyyMM" ) );
    1076                                timeA.add( Calendar.MONTH, 1 );
    1077                        }
    1078                }
    1079
    1080                 return list.toArray( new String[list.size()] ) ;
    1081        }
    1082}