    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.hayabusa.resource;
    017
    018import org.opengion.fukurou.util.HybsDateUtil;                                          // 6.4.2.0 (2016/01/29)
    019import org.opengion.hayabusa.common.HybsSystemException;
    020import static org.opengion.fukurou.system.HybsConst.CR ;                                // 6.1.0.0 (2014/12/26)
    021import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
    022
    023import java.util.Calendar;
    024import java.util.Map;
    025import java.util.SortedMap;
    026import java.util.TreeMap ;
    027import java.util.BitSet ;
    028
    029/**
    030 * 事業所(CDJGS) 毎の休日カレンダデータオブジェクトです。
    031 *
    032 * カレンダデータは、指定の事業所に関して、すべての休日情報を持っています。
    033 * 元のカレンダテーブル(GE13)の １日(DY1)～３１日(DY31)までの日付け欄に対して、
    034 * 休日日付けの 年月日 に対する、休日かどうかを判断できるだけの情報を保持します。
    035 * 具体的には、休日の年月日に対する List を持つことになります。
    036 * このクラスは、パッケージプライベートになっています。このオブジェクトを作成するのは、
    037 * CalendarFactory#getCalendarData( String ) で行います。引数は、事業所コード(cdjgs)です。
    038 * このカレンダオブジェクトを使用するには、事業所カレンダテーブル(GE13) を使用する
    039 * ことを許可しておく必要があります。
    040 * 許可は、システムパラメータ の USE_CALENDAR_DATABASE 属性を true に
    041 * 設定します(初期値は、互換性優先の false です。)
    042 * この、カレンダテーブルは、GE13 固定です。他のテーブルを使用する場合は、
    043 * ビュー等を作成する必要があります。
    044 * カレンダテーブル は、通常 DEFAULT DBIDを使用しますが、RESOURCE_CALENDAR_DBID
    045 * を設定することで、他のデータベースから読み取ることが可能になります。
    046 *
    047 * @og.rev 3.6.0.0 (2004/09/17) 新規作成
    048 * @og.group リソース管理
    049 *
    050 * @version  4.0
    051 * @author   Kazuhiko Hasegawa
    052 * @since    JDK5.0,
    053 */
    054class CalendarDBData implements CalendarData {
    055        private final SortedMap<String,BitSet> ymMap  = new TreeMap<>() ;       // 休日日付けデータを年月をキーに持ちます。
    056
    057        /**
    058         * コンストラクタ
    059         *
    060         * 配列文字列のデータを元に、CalendarDBDataオブジェクトを構築します。
    061         * このコンストラクタは、他のパッケージから呼び出せないように、
    062         * パッケージプライベートにしておきます。
    063         * 年月データは、連続である必要があります。
    064         * 途中に抜けがあるかどうかのチェックを行います。
    065         *
    066         * @param       data    データベース検索データ
    067         * @param       isFlat  縦持ち(false)か横持ち(true)の区別
    068         */
    069        CalendarDBData( final String[][] data,final boolean isFlat ) {
    070                if( isFlat ) {
    071                        callFlatTable( data );
    072                }
    073                else {
    074                        callVerticalTable( data );
    075                }
    076        }
    077
    078        /**
    079         * 横持ち(フラット)配列文字列のデータを元に、日付情報を構築します。
    080         * 年月データは、連続である必要があります。
    081         * 途中に抜けがあるかどうかのチェックを行います。
    082         *
    083         * @og.rev 3.8.8.0 (2007/12/22) 休日が全くない場合でも、日付設定する。
    084         * @og.rev 6.4.2.0 (2016/01/29) HybsDateUtil.getDatePlus( String,int ) を直接利用するように修正します。
    085         *
    086         * @param data 配列文字列のデータ
    087         *                     [0]:年月(YYYYMM) 200406 という形式
    088         *                     [1]～[31] 1日～31日のデータ
    089         *                           0:平日 / その他:休日
    090         */
    091        private void callFlatTable( final String[][] data ) {
    092                for( int ym=0; ym<data.length; ym++ ) {
    093                        final String yyyymm = data[ym][0] ;
    094                        if( yyyymm.length() != 6 ) {
    095                                final String errMsg = "年月(YYYYMM)は、YYYYMM(例：200406) という形式で指定して下さい。"
    096                                                        + " YYYYMM [" + yyyymm + "]" ;
    097                                throw new HybsSystemException( errMsg );
    098                        }
    099
    100                        final Calendar month = HybsDateUtil.getCalendar( yyyymm + "01" );               // 当月 6.4.2.0 (2016/01/29)
    101                        final int lastDay = month.getActualMaximum( Calendar.DAY_OF_MONTH );    // 月末日
    102
    103                        final BitSet ymData = new BitSet( lastDay+1 );
    104                        for( int d=1; d<=lastDay; d++ ) {
    105                                if( ! "0".equals( data[ym][d] ) ) {
    106                                        // 日付けのビットを立てます。
    107                                        ymData.set( d );
    108                                }
    109                        }
    110
    111                        // 日付けのキーがあり、休日設定があれば、有効月とみなし、セットします。
    112                        // 3.8.8.0 (2007/12/22) 休日が全くない場合でも、日付設定する。
    113                        ymMap.put( yyyymm,ymData );
    114                }
    115        }
    116
    117        /**
    118         * 縦持ち(バーティカル)配列文字列のデータを元に、日付情報を構築します。
    119         * 年月データは、連続である必要があります。
    120         * 途中に抜けがあるかどうかのチェックを行います。
    121         *
    122         * @og.rev 3.8.8.0 (2007/12/22) 休日が全くない場合でも、日付設定する。
    123         * @og.rev 6.4.2.0 (2016/01/29) HybsDateUtil.getDatePlus( String,int ) を直接利用するように修正します。
    124         *
    125         * @param data 配列文字列のデータ
    126         *                     [0]:年月(YYYYMMDD) 20040601 という形式
    127         *                     [1] 0:平日 / その他:休日
    128         */
    129        private void callVerticalTable( final String[][] data ) {
    130                String bkYyyymm = null;
    131                BitSet ymData = null;
    132                for( int ymd=0; ymd<data.length; ymd++ ) {
    133                        if( data[ymd][0].length() != 8 ) {
    134                                final String errMsg = "年月日(YYYYMMDD)は、YYYYMMDD(例：20040601) という形式で指定して下さい。"
    135                                                        + " YYYYMMDD [" + data[ymd][0] + "]" ;
    136                                throw new HybsSystemException( errMsg );
    137                        }
    138
    139                        final String yyyymm = data[ymd][0].substring( 0,6 ) ;
    140                        if( ! yyyymm.equals( bkYyyymm ) ) {     // 月のブレイク
    141                                // 日付けのキーがあり、休日設定があれば、有効月とみなし、セットします。
    142                                // 3.8.8.0 (2007/12/22) 休日が全くない場合でも、日付設定する。
    143                                if( ymData != null && bkYyyymm != null ) {
    144                                        ymMap.put( bkYyyymm,ymData );
    145                                }
    146                                bkYyyymm = yyyymm;
    147
    148                                final Calendar month = HybsDateUtil.getCalendar( yyyymm + "01" );               // 当月 6.4.2.0 (2016/01/29)
    149                                final int lastDay = month.getActualMaximum( Calendar.DAY_OF_MONTH );    // 月末日
    150
    151                                ymData = new BitSet( lastDay+1 );
    152                        }
    153
    154                        if( ! "0".equals( data[ymd][1] ) ) {
    155                                // 日付けのビットを立てます。
    156                                final int dt = Integer.parseInt( data[ymd][0].substring( 6 ) );
    157                                ymData.set( dt );
    158                        }
    159                }
    160                // 日付けのキーがあり、休日設定があれば、有効月とみなし、セットします。
    161                // 3.8.8.0 (2007/12/22) 休日が全くない場合でも、日付設定する。
    162                if( ymData != null && bkYyyymm != null ) {
    163                        ymMap.put( bkYyyymm,ymData );
    164                }
    165        }
    166
    167        /**
    168         * 指定の日付けが、休日かどうかを返します。
    169         * 指定の日付けが、データベースに設定されていない場合は、すべて休日を
    170         * 返すことで、存在しない事を示します。
    171         *
    172         * @param  day Calendar 指定の日付け
    173         *
    174         * @return      休日：true それ以外：false
    175         *
    176         */
    177        public boolean isHoliday( final Calendar day ) {
    178                final String yyyymm = cal2YM( day );
    179                final BitSet ymData = ymMap.get( yyyymm );
    180
    181                // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
    182                // 条件判定方法変更注意
    183                return ymData == null || ymData.get( day.get( Calendar.DATE ) );
    184        }
    185
    186        /**
    187         * 指定の日付けから、範囲の間に、本日を含むかどうかを返します。
    188         * 指定の日付けが、キャッシュしているデータの最大と最小の間に
    189         * 存在しない場合は、常に false になります。
    190         * 判定は、年月日の項目のみで比較し、時分秒は無視します。
    191         *
    192         * @og.rev 3.7.1.1 (2005/05/31) 新規追加
    193         * @og.rev 3.8.8.6 (2007/04/20) today を毎回求めます。(キャッシュ対策)
    194         *
    195         * @param  day Calendar 指定の開始日付け
    196         * @param       scope   範囲の日数
    197         *
    198         * @return      本日：true それ以外：false
    199         */
    200        public boolean isContainedToday( final Calendar day,final int scope ) {
    201                final boolean rtnFlag;
    202
    203                final Calendar today = Calendar.getInstance();
    204                today.set( Calendar.HOUR_OF_DAY ,12 );  // 昼にセット
    205                today.set( Calendar.MINUTE ,0 );
    206                today.set( Calendar.SECOND ,0 );
    207
    208                if( scope == 1 ) {
    209                        // false の確率の高い方から、比較します。
    210                        rtnFlag = day.get( Calendar.DATE )  == today.get( Calendar.DATE  ) &&
    211                                                day.get( Calendar.MONTH ) == today.get( Calendar.MONTH ) &&
    212                                                day.get( Calendar.YEAR )  == today.get( Calendar.YEAR  ) ;
    213                }
    214                else {
    215                        final Calendar next = (Calendar)day.clone();
    216                        next.add( Calendar.DATE,scope );
    217                        rtnFlag = day.before( today ) && next.after( today ) ;
    218                }
    219                return rtnFlag ;
    220        }
    221
    222        /**
    223         * 指定の開始、終了日の期間に、平日(稼働日)が何日あるか求めます。
    224         * 調査月が、データベース上に存在していない場合は、エラーとします。
    225         * 開始と終了が同じ日の場合は、１を返します。
    226         *
    227         * @param  start Calendar 開始日付け(稼働日に含めます)
    228         * @param  end   Calendar 終了日付け(稼働日に含めます)
    229         *
    230         * @return      稼働日数
    231         *
    232         */
    233        public int getKadoubisu( final Calendar start,final Calendar end ) {
    234
    235                final String stYM = cal2YM( start );
    236                final String edYM = cal2YM( end );
    237
    238                final SortedMap<String,BitSet> subMap = ymMap.subMap( stYM,edYM+"\0" ) ;        // 終了キーはそのままでは含まれないため。
    239
    240                @SuppressWarnings("rawtypes")
    241                final Map.Entry[] entrys = subMap.entrySet().toArray( new Map.Entry[subMap.size()] );
    242
    243                int holidaySu = 0;
    244                for( int i=0; i<entrys.length; i++ ) {
    245                        final String ym = (String)entrys[i].getKey();
    246                        BitSet bt ;
    247                        if( stYM.equals( ym ) ) {
    248                                bt = ((BitSet)entrys[i].getValue()).get( start.get( Calendar.DATE ),31 );
    249                        }
    250                        else if( edYM.equals( ym ) ) {
    251                                bt = ((BitSet)entrys[i].getValue()).get( 1,end.get( Calendar.DATE )+1 );
    252                        }
    253                        else {
    254                                bt = (BitSet)entrys[i].getValue();
    255                        }
    256                        holidaySu += bt.cardinality() ;
    257                }
    258
    259                final long diff = start.getTimeInMillis() - end.getTimeInMillis() ;
    260                final int dayCount = (int)(diff/(1000*60*60*24)) + 1;   // end も含むので＋１必要
    261
    262                return dayCount - holidaySu ;
    263        }
    264
    265        /**
    266         * 指定の開始日に平日のみ期間を加算して求められる日付けを返します。
    267         * これは、実稼働日計算に使用します。
    268         * 例えば、start=20040810 , span=5 で、休日がなければ、10,11,12,13,14 となり、
    269         * 20040815 を返します。
    270         * 指定の日付けや、期間加算後の日付けが、キャッシュしているデータの
    271         * 最大と最小の間に存在しない場合は、エラーとします。
    272         *
    273         * @param  start Calendar   開始日付け(YYYYMMDD 形式)
    274         * @param       span    稼動期間
    275         *
    276         * @return      開始日から稼動期間を加算した日付け(当日を含む)
    277         *
    278         */
    279        public Calendar getAfterDay( final Calendar start,final int span ) {
    280                final Calendar today = (Calendar)(start.clone());
    281                int suSpan = span ;
    282                while( suSpan > 0 ) {
    283                        if( ! isHoliday( today ) ) { suSpan--; }
    284                        today.add(Calendar.DATE, 1);            // 日にちを進める。
    285                }
    286                return today ;
    287        }
    288
    289        /**
    290         * カレンダオブジェクトより、年月を YYYYMM 形式にした 文字列を返します。
    291         *
    292         * @param       cal     カレンダオブジェクト
    293         * @return  カレンダのYYYYMM 文字列
    294         * @og.rtnNotNull
    295         */
    296        private String cal2YM( final Calendar cal ) {
    297                return String.valueOf(
    298                                        cal.get( Calendar.YEAR ) * 100 +
    299                                        cal.get( Calendar.MONTH ) + 1 );
    300        }
    301
    302        /**
    303         * オブジェクトの識別子として，詳細なカレンダ情報を返します。
    304         *
    305         * @return  詳細なカレンダ情報
    306         * @og.rtnNotNull
    307         */
    308        @Override
    309        public String toString() {
    310                final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE )
    311                        .append( "CLASS   : ").append( getClass().getName() ).append( CR );     // クラス名
    312
    313                @SuppressWarnings("rawtypes")
    314                final Map.Entry[] entrys = ymMap.entrySet().toArray( new Map.Entry[ymMap.size()] );
    315
    316                for( int i=0; i<entrys.length; i++ ) {
    317                        final String yyyymm = (String)entrys[i].getKey();
    318
    319                        rtn.append( yyyymm )
    320                                .append( ':' )                                          // 6.0.2.5 (2014/10/31) char を append する。
    321                                .append( (BitSet)entrys[i].getValue() )
    322                                .append( CR );
    323                }
    324
    325                return rtn.toString();
    326        }
    327}