    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.plugin.table;
    017
    018import java.util.Calendar;                                                                      // 7.0.1.3 (2018/11/12)
    019
    020import org.opengion.hayabusa.db.AbstractTableFilter;
    021import org.opengion.hayabusa.db.DBTableModel;
    022
    023import org.opengion.fukurou.util.ErrorMessage;
    024import org.opengion.fukurou.util.StringUtil;
    025import org.opengion.fukurou.util.HybsDateUtil;                          // 7.0.1.3 (2018/11/12)
    026import org.opengion.fukurou.system.DateSet;                                     // 7.0.1.3 (2018/11/12)
    027
    028/**
    029 * TableFilter_KEY_BREAK は､TableFilter ｲﾝﾀｰﾌｪｰｽを継承した､DBTableModel 処理用の
    030 * 実装ｸﾗｽです｡
    031 *
    032 * ここでは､指定のｶﾗﾑに対して､ｷｰﾌﾞﾚｲｸが発生したときのﾃﾞｰﾀ処理方法を指定できます｡
    033 * 主として､ｸﾞﾙｰﾋﾟﾝｸﾞ処理を行うのですが､ｿｰﾄされﾃﾞｰﾀの並び順で､ｷｰﾌﾞﾚｲｸするため､
    034 * 同一ｷｰが存在していても､並び順が離れている場合､別のｷｰとしてﾌﾞﾚｲｸします｡
    035 *
    036 * GROUP_KEY  : ｷｰﾌﾞﾚｲｸの判定を行うｶﾗﾑを､CSV形式で設定します｡
    037 * OUT_TYPE   : 出力するﾃﾞｰﾀのﾀｲﾌﾟを指定します｡
    038 *                first : 最初のﾃﾞｰﾀ(ﾌﾞﾚｲｸ直後のﾃﾞｰﾀ)を出力します｡(初期値)
    039 *                last  : 最後のﾃﾞｰﾀ(ﾌﾞﾚｲｸ直前のﾃﾞｰﾀ)を出力します｡
    040 *                range : 最初のﾃﾞｰﾀと最後のﾃﾞｰﾀを出力します｡
    041 *
    042 * firstは､ｷｰﾌﾞﾚｲｸ時のﾃﾞｰﾀを残します｡つまり､ｷｰの最初に現れたﾃﾞｰﾀです｡
    043 * lastは､ｷｰﾌﾞﾚｲｸの直前のﾃﾞｰﾀを残します｡これは､同一ｷｰの最後のﾃﾞｰﾀということになります｡
    044 * rangeは､firstと､last つまり､同値ｷｰの最初と最後のﾃﾞｰﾀを残します｡
    045 *
    046 * もし､ｷｰが､1行だけの場合､firstも､lastも､同じ行を指すことになります｡
    047 * その場合､rangeは､その1行だけになります(2行出力されません)｡
    048 *
    049 * 例：機種と日付と､状況Fがあったとして､日付､機種､状況F でｿｰﾄし､機種をｸﾞﾙｰﾌﾟｷｰ､
    050 * 状況Fをﾌﾞﾚｲｸｷｰとすれば､日付の順に､機種の中で､状況Fがﾌﾞﾚｰｸしたときのみ､
    051 * ﾃﾞｰﾀを残す､ということが可能になります｡7.0.0.1 (2018/10/09) Delete
    052 *
    053 * OUT_TYPE に､lastか､range を指定した場合のみ､最大､最小､平均､中間､個数の集計処理が行えます｡
    054 * これらの設定は､指定のｶﾗﾑのﾃﾞｰﾀに反映されます｡
    055 * MIN_CLM  : ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの最小値をﾃﾞｰﾀに書き込みます｡
    056 * MAX_CLM  : ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの最大値をﾃﾞｰﾀに書き込みます｡
    057 * AVG_CLM  : ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの平均値をﾃﾞｰﾀに書き込みます｡
    058 * MID_CLM  : ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの最小値と最大値の中間の値をﾃﾞｰﾀに書き込みます｡
    059 * DIF_CLM  : ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの最大値から最小値を引いた値(差)をﾃﾞｰﾀに書き込みます｡8.0.1.2 (2021/11/19)
    060 * CNT_CLM  : ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑのﾃﾞｰﾀ件数をﾃﾞｰﾀに書き込みます｡
    061 *
    062 * これらのｶﾗﾑの値は､数値で表現できるもので無ければなりません｡
    063 * 例えば､20180101000000 のような､日付でも数字のみなら､OKです｡
    064 *
    065 * 8.0.1.2 (2021/11/19) DIF_CLM 差分計算
    066 *  8桁か14桁で、先頭"20"の場合は、日付型と判定します。
    067 *  その場合、8桁は、経過日数を返し、14桁は、MM/dd HH:mm 形式で返します。
    068 *
    069 * ﾊﾟﾗﾒｰﾀは､tableFilterﾀｸﾞの keys, vals にそれぞれ記述するか､BODY 部にCSS形式で記述します｡
    070 *
    071 * @og.formSample
    072 * ●形式：
    073 *              ① &lt;og:tableFilter classId="KEY_BREAK"
    074 *                              keys="GROUP_KEY,OUT_TYPE"
    075 *                              vals='"CLM5,CLM6....",first' /&gt;
    076 *
    077 *              ② &lt;og:tableFilter classId="KEY_BREAK" &gt;
    078 *                               {
    079 *                                       GROUP_KEY  : CLM5,CLM6....     ;
    080 *                                       OUT_TYPE : first                       ;
    081 *                               }
    082 *                 &lt;/og:tableFilter&gt;
    083 *
    084 * @og.rev 6.7.9.1 (2017/05/19) 新規追加
    085 * @og.rev 7.0.0.1 (2018/10/09) ｸﾞﾙｰﾌﾟで､まとめる処理を止めます｡
    086 * @og.rev 7.0.1.1 (2018/10/22) ﾛｼﾞｯｸ見直し
    087 *
    088 * @version  6.7  2017/05/19
    089 * @author   Kazuhiko Hasegawa
    090 * @since    JDK1.8,
    091 */
    092public class TableFilter_KEY_BREAK extends AbstractTableFilter {
    093        /** このﾌﾟﾛｸﾞﾗﾑのVERSION文字列を設定します｡ {@value} */
    094        private static final String VERSION = "8.1.2.1 (2022/03/25)" ;
    095
    096        /** 8.0.1.2 (2021/11/19) 日単位変換係数 */
    097        private static final int MILLIS_OF_DAY = 1000 * 60 * 60 * 24;
    098
    099        /**
    100         * ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰ
    101         *
    102         * @og.rev 6.7.9.1 (2017/05/19) 新規追加
    103         * @og.rev 7.0.0.1 (2018/10/09) 最小,最大,平均,件数 を集計するためのｷｰﾜｰﾄﾞ追加
    104         * @og.rev 7.0.1.3 (2018/11/12) MID_CLM(最小値と最大値の中間の値)のｷｰﾜｰﾄﾞ追加
    105         * @og.rev 8.0.1.2 (2021/11/19) DIF_CLM(最大値から最小値を引いた値(差))のｷｰﾜｰﾄﾞ追加
    106         */
    107        public TableFilter_KEY_BREAK() {
    108                super();
    109                initSet( "GROUP_KEY"    , "ｷｰﾌﾞﾚｲｸの判定を行うｶﾗﾑを､CSV形式で設定します｡" );
    110                initSet( "OUT_TYPE"     , "出力するﾃﾞｰﾀのﾀｲﾌﾟを指定[first/last/range]を指定します｡(初期値:first 最初のﾃﾞｰﾀ)"        );
    111                initSet( "MIN_CLM"      , "ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの最小値をﾃﾞｰﾀに書き込みます｡"   );
    112                initSet( "MAX_CLM"      , "ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの最大値をﾃﾞｰﾀに書き込みます｡"   );
    113                initSet( "AVG_CLM"      , "ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの平均値をﾃﾞｰﾀに書き込みます｡"   );
    114                initSet( "MID_CLM"      , "ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの最小値と最大値の中間の値をﾃﾞｰﾀに書き込みます｡"  );
    115                initSet( "DIF_CLM"      , "ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑの最大値から最小値を引いた値(差)をﾃﾞｰﾀに書き込みます｡"      );      // 8.0.1.2 (2021/11/19)
    116                initSet( "CNT_CLM"      , "ｷｰﾌﾞﾚｲｸ時に､指定のｶﾗﾑのﾃﾞｰﾀ件数をﾃﾞｰﾀに書き込みます｡"        );
    117        }
    118
    119        /**
    120         * DBTableModel処理を実行します｡
    121         *
    122         * @og.rev 6.7.9.1 (2017/05/19) 新規追加
    123         * @og.rev 7.0.0.1 (2018/10/09) 最小,最大,平均,件数 を集計するためのｷｰﾜｰﾄﾞ追加
    124         * @og.rev 7.0.1.1 (2018/10/22) ﾛｼﾞｯｸ見直し
    125         * @og.rev 7.0.1.3 (2018/11/12) MID_CLM(最小値と最大値の中間の値)のｷｰﾜｰﾄﾞ追加
    126         * @og.rev 7.2.4.0 (2020/05/11) MIN_CLMとMAX_CLMが不定の場合は､ｾﾞﾛ文字列をｾｯﾄします｡
    127         * @og.rev 8.0.1.2 (2021/11/19) DIF_CLM(最大値から最小値を引いた値(差))のｷｰﾜｰﾄﾞ追加
    128         * @og.rev 8.1.2.1 (2022/03/25) OUT_TYPE="first" 時に最小,最大,平均,件数の集計ができるように機能追加
    129         *
    130         * @return 処理結果のDBTableModel
    131         */
    132        public DBTableModel execute() {
    133                final DBTableModel table  = getDBTableModel();
    134                final DBTableModel rtnTbl = table.newModel();                           // 削除ではなく､追加していきます｡
    135                final int rowCnt = table.getRowCount();
    136                if( rowCnt == 0 ) { return rtnTbl; }                                            // 7.0.1.3 (2018/11/12) row<=rowCnt を追加したので､0件なら即終了
    137
    138                final String[]  brkClms = StringUtil.csv2Array( getValue( "GROUP_KEY" ) );
    139
    140                // 7.0.0.1 (2018/10/09) 最小,最大,平均,件数 を集計するためのｷｰﾜｰﾄﾞ追加
    141                final String    outType = StringUtil.nval( getValue( "OUT_TYPE"  ), "first" ) ;
    142
    143                final boolean   useFirst = "first".equalsIgnoreCase( outType ) || "range".equalsIgnoreCase( outType );  // firstかrange時に使用
    144                final boolean   useLast  = "last".equalsIgnoreCase( outType )  || "range".equalsIgnoreCase( outType ) ; // lastかrange 時に使用
    145
    146                // 7.0.0.1 (2018/10/09) 最小,最大,平均,件数 を集計するためのｷｰﾜｰﾄﾞ追加(useLast=true のときのみ使用)
    147                final int       minClmNo = table.getColumnNo( getValue( "MIN_CLM"  ), false ) ; // ｶﾗﾑが存在しなければ､-1
    148                final int       maxClmNo = table.getColumnNo( getValue( "MAX_CLM"  ), false ) ; // ｶﾗﾑが存在しなければ､-1
    149                final int       avgClmNo = table.getColumnNo( getValue( "AVG_CLM"  ), false ) ; // ｶﾗﾑが存在しなければ､-1
    150                final int       midClmNo = table.getColumnNo( getValue( "MID_CLM"  ), false ) ; // 7.0.1.3 (2018/11/12) ｶﾗﾑが存在しなければ､-1
    151                final int       difClmNo = table.getColumnNo( getValue( "DIF_CLM"  ), false ) ; // 8.0.1.2 (2021/11/19) ｶﾗﾑが存在しなければ､-1
    152                final int       cntClmNo = table.getColumnNo( getValue( "CNT_CLM"  ), false ) ; // ｶﾗﾑが存在しなければ､-1
    153
    154                final int[]    brkClmNo = new int[brkClms.length];                      // ﾌﾞﾚｲｸｷｰｶﾗﾑの番号
    155
    156                for( int i=0; i<brkClms.length; i++ ) {
    157                        brkClmNo[i] = table.getColumnNo( brkClms[i],false );    // ｶﾗﾑが存在しなければ､-1
    158                }
    159
    160                // 7.0.0.1 (2018/10/09) 最小,最大,平均,件数 を集計するためのｷｰﾜｰﾄﾞ追加(useLast=true のときのみ使用)
    161                double  minData  = Double.POSITIVE_INFINITY ;           // 仮数部の桁数の限界は15桁なので､日付型(14桁)は､処理できる｡
    162                double  maxData  = Double.NEGATIVE_INFINITY ;
    163                double  total    = 0.0 ;
    164                int             cntData  = 0 ;          //
    165                boolean isLong   = true;        // ﾃﾞｰﾀに､少数点以下をつけるかどうかの判定です｡
    166                double  midMin   = Double.POSITIVE_INFINITY ;
    167                double  midMax   = Double.NEGATIVE_INFINITY ;
    168                double  difMin   = Double.POSITIVE_INFINITY ;           // 8.0.1.2 (2021/11/19)
    169                double  difMax   = Double.NEGATIVE_INFINITY ;           // 8.0.1.2 (2021/11/19)
    170
    171                String oldBlkKeys = null;       // 前回ﾌﾞﾚｲｸｷｰの値
    172
    173                String[] oldData = null;
    174                // 7.0.1.3 (2018/11/12) 最後のﾃﾞｰﾀの処理を行うために､row<=rowCnt と1回余計に回します｡
    175                for( int row=0; row<=rowCnt; row++ ) {
    176                        final String[] data = row == rowCnt ? null : table.getValues( row );            // row<=rowCnt の影響
    177                        try {
    178                                final String brkKeys = getKeys( brkClmNo , data );              // ﾌﾞﾚｰｸｷｰ(data==nullの場合､ｾﾞﾛ文字列)
    179                                if( !brkKeys.equalsIgnoreCase( oldBlkKeys ) ) {                 // ｷｰﾌﾞﾚｲｸ
    180                                        // 初回必ずﾌﾞﾚｲｸするので、row==0 時は処理しない。
    181                                        if( row>0 ) {
    182                                                // 7.2.4.0 (2020/05/11) MIN_CLMとMAX_CLMが不定の場合は､ｾﾞﾛ文字列をｾｯﾄします｡
    183                                                if( minClmNo >= 0 ) {
    184                                                        if( minData == Double.POSITIVE_INFINITY ) {
    185                                                                oldData[minClmNo] = "";                 // 7.2.4.0 (2020/05/11)
    186                                                        }
    187                                                        else {
    188                                                                oldData[minClmNo] = isLong ? String.valueOf( Math.round( minData ) ) : String.valueOf( minData ) ;
    189                                                        }
    190                                                }
    191                                                if( maxClmNo >= 0 ) {
    192                                                        if( maxData == Double.NEGATIVE_INFINITY ) {
    193                                                                oldData[maxClmNo] = "";                 // 7.2.4.0 (2020/05/11)
    194                                                        }
    195                                                        else {
    196                                                                oldData[maxClmNo] = isLong ? String.valueOf( Math.round( maxData ) ) : String.valueOf( maxData ) ;
    197                                                        }
    198                                                }
    199                                                if( avgClmNo >= 0 ) { oldData[avgClmNo] = String.format( "%.3f", total/cntData ); }
    200                                                if( midClmNo >= 0 ) { oldData[midClmNo] = getMiddle( midMin,midMax ); }
    201                                                if( difClmNo >= 0 ) { oldData[difClmNo] = getDifference( difMin,difMax ); }     // 8.0.1.2 (2021/11/19)
    202                                                if( cntClmNo >= 0 ) { oldData[cntClmNo] = String.valueOf( cntData ); }
    203
    204                                                minData = Double.POSITIVE_INFINITY  ;
    205                                                maxData = Double.NEGATIVE_INFINITY  ;
    206                                                total   = 0.0 ;
    207                                                midMin  = Double.POSITIVE_INFINITY  ;
    208                                                midMax  = Double.NEGATIVE_INFINITY  ;
    209                                                difMin  = Double.POSITIVE_INFINITY  ;           // 8.0.1.2 (2021/11/19)
    210                                                difMax  = Double.NEGATIVE_INFINITY  ;           // 8.0.1.2 (2021/11/19)
    211
    212                                                // 8.1.2.1 (2022/03/25) OUT_TYPE="first" 時に最小,最大,平均,件数の集計ができるように機能追加
    213                        //                      if( useLast ) {
    214                                                        // useFirst=true で､cntData == 1 の場合は､First行は削除します(1件を2件に増やさない)｡
    215                                                        if( useFirst ) {
    216                                                                final int rCnt = rtnTbl.getRowCount();
    217                                                                if( cntData == 1 ) {                                            // 1行しかない場合は､First行は削除します(1件を2件に増やさない)
    218                                                                        rtnTbl.removeValue( rCnt-1 );
    219                                                                }
    220                                                                else {
    221                                                                        // useLast && useFirst ⇒ range 指定の処理。 前のﾃﾞｰﾀ=First行に､最大､最小等のﾃﾞｰﾀを反映させます｡
    222                                                                        final String[] fstData = rtnTbl.getValues( rCnt-1 );
    223                                                                        if( minClmNo >= 0 ) { fstData[minClmNo] = oldData[minClmNo]; }
    224                                                                        if( maxClmNo >= 0 ) { fstData[maxClmNo] = oldData[maxClmNo]; }
    225                                                                        if( avgClmNo >= 0 ) { fstData[avgClmNo] = oldData[avgClmNo]; }
    226                                                                        if( midClmNo >= 0 ) { fstData[midClmNo] = oldData[midClmNo]; }
    227                                                                        if( difClmNo >= 0 ) { fstData[difClmNo] = oldData[difClmNo]; }          // 8.0.1.2 (2021/11/19)
    228                                                                        if( cntClmNo >= 0 ) { fstData[cntClmNo] = oldData[cntClmNo]; }
    229                                                                }
    230                                                        }
    231                        //                              rtnTbl.addColumnValues( oldData );                      // ﾌﾞﾚｲｸした一つ前＝最後のﾃﾞｰﾀ
    232                        //                      }
    233
    234                                                if( useLast ) {
    235                                                        rtnTbl.addColumnValues( oldData );                      // ﾌﾞﾚｲｸした一つ前＝最後のﾃﾞｰﾀ
    236                                                }
    237
    238                                                if( row == rowCnt ) { break; }                                  // 最後のﾃﾞｰﾀの処理を行うために､row<=rowCnt と1回余計に回します｡
    239                                        }
    240
    241                                        if( useFirst ) {                                                                        // useLast=true で､cntData == 1 の場合は､登録しません
    242                                                rtnTbl.addColumnValues( data );                                 // ﾌﾞﾚｲｸ時のﾃﾞｰﾀを登録します｡
    243                                        }
    244
    245                                        oldBlkKeys = brkKeys;
    246                                        cntData    = 0 ;
    247                                }
    248                                oldData = data;                                                                                 // 一つ前のﾃﾞｰﾀ
    249                                cntData++;                              // 毎回､ｶﾗﾑのある無しを判定するより､早そうなので常にｶｳﾝﾄしておきます｡
    250
    251                                // ﾌﾞﾚｲｸ時も集計処理は行います｡
    252                                if( minClmNo >= 0 && !StringUtil.isNull( data[minClmNo] ) ) {
    253                                        if( isLong && data[minClmNo].indexOf( '.' ) >= 0 ) { isLong = false; }                  // 一度､false になると､戻らない｡
    254                                        minData = Math.min( minData, Double.parseDouble( data[minClmNo] ) );
    255                                }
    256                                if( maxClmNo >= 0 && !StringUtil.isNull( data[maxClmNo] ) ) {
    257                                        if( isLong && data[maxClmNo].indexOf( '.' ) >= 0 ) { isLong = false; }                  // 一度､false になると､戻らない｡
    258                                        maxData = Math.max( maxData, Double.parseDouble( data[maxClmNo] ) );
    259                                }
    260                                if( avgClmNo >= 0 && !StringUtil.isNull( data[avgClmNo] ) ) {
    261                                        total += Double.parseDouble( data[avgClmNo] );
    262                                }
    263                                if( midClmNo >= 0 && !StringUtil.isNull( data[midClmNo] ) ) {
    264                                        final double mid = Double.parseDouble( data[midClmNo] );
    265                                        midMin = Math.min( midMin, mid );
    266                                        midMax = Math.max( midMax, mid );
    267                                }
    268                                if( difClmNo >= 0 && !StringUtil.isNull( data[difClmNo] ) ) {           // 8.0.1.2 (2021/11/19)
    269                                        final double dif = Double.parseDouble( data[difClmNo] );
    270                                        difMin = Math.min( difMin, dif );
    271                                        difMax = Math.max( difMax, dif );
    272                                }
    273                        }
    274                        catch( final RuntimeException ex ) {    // そのまま､継続して処理を行う｡
    275                                // 6.5.0.1 (2016/10/21) ErrorMessage をまとめるのと､直接 Throwable を渡します｡
    276                                makeErrorMessage( "TableFilter_KEY_BREAK Error",ErrorMessage.NG )
    277                                        .addMessage( row+1,ErrorMessage.NG,"KEY_BREAK" , StringUtil.array2csv( data ) )
    278                                        .addMessage( ex );
    279                        }
    280                }
    281
    282                return rtnTbl;
    283        }
    284
    285        /**
    286         * 最小値と最大値の中間の値の文字列を作成します｡
    287         *
    288         * 特殊系で､8桁か､14桁の場合､日付文字として中間の日付を求めます｡
    289         *
    290         * @og.rev 7.0.1.3 (2018/11/12) MID_CLM(最小値と最大値の中間の値)のｷｰﾜｰﾄﾞ追加
    291         *
    292         * @param min 最小値
    293         * @param max 最大値
    294         * @return 中間の値の文字列
    295         */
    296        private String getMiddle( final double min , final double max ) {
    297                final String minStr = String.valueOf( Math.round( min ) );              // 14桁の場合､2.0181103000000E13 見たいな表記になるため｡
    298                final String maxStr = String.valueOf( Math.round( max ) );
    299                final int minLen = minStr.length();
    300
    301                final String midStr ;
    302                // 2000 年問題!? 先頭が "20" の場合は、日付型と判定する。
    303                if( minLen == maxStr.length() && ( minLen == 8 || minLen == 14 )
    304                                && minStr.startsWith("20") && maxStr.startsWith("20") ) {
    305                        final Calendar minCal = HybsDateUtil.getCalendar( minStr );
    306                        final Calendar maxCal = HybsDateUtil.getCalendar( maxStr );
    307                        final long     midTim = ( maxCal.getTimeInMillis() + minCal.getTimeInMillis() ) / 2 ;
    308
    309                        if( minLen == 8 ) {
    310                                midStr = DateSet.getDate( midTim , "yyyyMMdd" );
    311                        }
    312                        else {  // 14桁しかありえない
    313                                midStr = DateSet.getDate( midTim , "yyyyMMddHHmmss" );
    314                        }
    315                }
    316                else {
    317                        midStr = String.format( "%.3f", ( max + min ) / 2.0 );          // 日付型でなければ､minStr,maxStr は使わないので｡
    318                }
    319
    320                return midStr;
    321        }
    322
    323        /**
    324         * 最大値から最小値を引いた値(差)の文字列を作成します｡
    325         *
    326         * 特殊系で､8桁か､14桁の場合､日付文字として経過日数を求めます。
    327         *
    328         * @og.rev 8.0.1.2 (2021/11/19) DIF_CLM(最大値から最小値を引いた値(差))のｷｰﾜｰﾄﾞ追加
    329         *
    330         * @param min 最小値
    331         * @param max 最大値
    332         * @return 最大値から最小値を引いた値(差)の文字列
    333         */
    334        private String getDifference( final double min , final double max ) {
    335                final String minStr = String.valueOf( Math.round( min ) );              // 14桁の場合､2.0181103000000E13 見たいな表記になるため｡
    336                final String maxStr = String.valueOf( Math.round( max ) );
    337                final int minLen = minStr.length();
    338
    339                final String midStr ;
    340                // 2000 年問題!? 先頭が "20" の場合は、日付型と判定する。
    341                if( minLen == maxStr.length() && ( minLen == 8 || minLen == 14 )
    342                                && minStr.startsWith("20") && maxStr.startsWith("20") ) {
    343                        final Calendar minCal = HybsDateUtil.getCalendar( minStr );
    344                        final Calendar maxCal = HybsDateUtil.getCalendar( maxStr );
    345
    346                        final long difTim = maxCal.getTimeInMillis() - minCal.getTimeInMillis() ;
    347
    348                        if( minLen == 8 ) {
    349                                midStr = String.format( "%d", (int)difTim/MILLIS_OF_DAY );
    350                        }
    351                        else {  // 14桁しかありえない
    352                                midStr = DateSet.getDate( difTim , "MM/dd HH:mm" );
    353                        }
    354                }
    355                else {
    356                        midStr = String.format( "%.3f", max - min );    // 日付型でなければ､minStr,maxStr は使わないので｡
    357                }
    358
    359                return midStr;
    360        }
    361
    362        /**
    363         * ｷｰの配列ｱﾄﾞﾚｽと､１行分のﾃﾞｰﾀ配列から､ｷｰとなる文字列を作成します｡
    364         *
    365         * @og.rev 6.7.9.1 (2017/05/19) 新規追加
    366         * @og.rev 7.0.1.3 (2018/11/12) 最後のﾃﾞｰﾀの処理を行うために､row<=rowCnt と1回余計に回す対応
    367         *
    368         * @param clms ｷｰの配列ｱﾄﾞﾚｽ
    369         * @param rowData １行分のﾃﾞｰﾀ配列
    370         * @return ｷｰとなる文字列
    371         */
    372        private String getKeys( final int[] clms , final String[] rowData ) {
    373                if( rowData == null ) { return ""; }                                    // rowData がnull の場合は､ｷｰﾌﾞﾚｲｸとなる
    374
    375                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    376                // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
    377                for( final int clm : clms ) {
    378                        if( clm >= 0 ) {
    379                                buf.append( rowData[clm] ).append( ':' );
    380                        }
    381                }
    382
    383                return buf.toString();
    384        }
    385}