    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         * 指定の日付けが、キャッシュしているデータの最大と最小の間に
    190         * 存在しない場合は、常に false になります。
    191         * 判定は、年月日の項目のみで比較し、時分秒は無視します。
    192         *
    193         * @og.rev 3.7.1.1 (2005/05/31) 新規追加
    194         * @og.rev 3.8.8.6 (2007/04/20) today を毎回求めます。(キャッシュ対策)
    195         *
    196         * @param  day Calendar 指定の開始日付け
    197         * @param       scope   範囲の日数
    198         *
    199         * @return      本日：true それ以外：false
    200         */
    201        public boolean isContainedToday( final Calendar day,final int scope ) {
    202                final boolean rtnFlag;
    203
    204                final Calendar today = Calendar.getInstance();
    205                today.set( Calendar.HOUR_OF_DAY ,12 );  // 昼にセット
    206                today.set( Calendar.MINUTE ,0 );
    207                today.set( Calendar.SECOND ,0 );
    208
    209                if( scope == 1 ) {
    210                        // false の確率の高い方から、比較します。
    211                        rtnFlag = day.get( Calendar.DATE )  == today.get( Calendar.DATE  ) &&
    212                                                day.get( Calendar.MONTH ) == today.get( Calendar.MONTH ) &&
    213                                                day.get( Calendar.YEAR )  == today.get( Calendar.YEAR  ) ;
    214                }
    215                else {
    216                        final Calendar next = (Calendar)day.clone();
    217                        next.add( Calendar.DATE,scope );
    218                        rtnFlag = day.before( today ) && next.after( today ) ;
    219                }
    220                return rtnFlag ;
    221        }
    222
    223        /**
    224         * 指定の開始、終了日の期間に、平日(稼働日)が何日あるか求めます。
    225         * 調査月が、データベース上に存在していない場合は、エラーとします。
    226         * 開始と終了が同じ日の場合は、１を返します。
    227         *
    228         * @param  start Calendar 開始日付け(稼働日に含めます)
    229         * @param  end   Calendar 終了日付け(稼働日に含めます)
    230         *
    231         * @return      稼働日数
    232         *
    233         */
    234        public int getKadoubisu( final Calendar start,final Calendar end ) {
    235
    236                final String stYM = cal2YM( start );
    237                final String edYM = cal2YM( end );
    238
    239                final SortedMap<String,BitSet> subMap = ymMap.subMap( stYM,edYM+"\0" ) ;        // 終了キーはそのままでは含まれないため。
    240
    241                @SuppressWarnings("rawtypes")
    242                final Map.Entry[] entrys = subMap.entrySet().toArray( new Map.Entry[subMap.size()] );
    243
    244                int holidaySu = 0;
    245                for( int i=0; i<entrys.length; i++ ) {
    246                        final String ym = (String)entrys[i].getKey();
    247                        BitSet bt ;
    248                        if( stYM.equals( ym ) ) {
    249                                bt = ((BitSet)entrys[i].getValue()).get( start.get( Calendar.DATE ),31 );
    250                        }
    251                        else if( edYM.equals( ym ) ) {
    252                                bt = ((BitSet)entrys[i].getValue()).get( 1,end.get( Calendar.DATE )+1 );
    253                        }
    254                        else {
    255                                bt = (BitSet)entrys[i].getValue();
    256                        }
    257                        holidaySu += bt.cardinality() ;
    258                }
    259
    260                final long diff = start.getTimeInMillis() - end.getTimeInMillis() ;
    261                final int dayCount = (int)(diff/(1000*60*60*24)) + 1;   // end も含むので＋１必要
    262
    263                return dayCount - holidaySu ;
    264        }
    265
    266        /**
    267         * 指定の開始日に平日のみ期間を加算して求められる日付けを返します。
    268         * これは、実稼働日計算に使用します。
    269         * 例えば、start=20040810 , span=5 で、休日がなければ、10,11,12,13,14 となり、
    270         * 20040815 を返します。
    271         * 指定の日付けや、期間加算後の日付けが、キャッシュしているデータの
    272         * 最大と最小の間に存在しない場合は、エラーとします。
    273         *
    274         * @param  start Calendar   開始日付け(YYYYMMDD 形式)
    275         * @param       span    稼動期間
    276         *
    277         * @return      開始日から稼動期間を加算した日付け(当日を含む)
    278         *
    279         */
    280        public Calendar getAfterDay( final Calendar start,final int span ) {
    281                final Calendar today = (Calendar)(start.clone());
    282                int suSpan = span ;
    283                while( suSpan > 0 ) {
    284                        if( ! isHoliday( today ) ) { suSpan--; }
    285                        today.add(Calendar.DATE, 1);            // 日にちを進める。
    286                }
    287                return today ;
    288        }
    289
    290        /**
    291         * カレンダオブジェクトより、年月を YYYYMM 形式にした 文字列を返します。
    292         *
    293         * @param       cal     カレンダオブジェクト
    294         * @return  カレンダのYYYYMM 文字列
    295         * @og.rtnNotNull
    296         */
    297        private String cal2YM( final Calendar cal ) {
    298                return String.valueOf(
    299                                        cal.get( Calendar.YEAR ) * 100 +
    300                                        cal.get( Calendar.MONTH ) + 1 );
    301        }
    302
    303        /**
    304         * オブジェクトの識別子として，詳細なカレンダ情報を返します。
    305         *
    306         * @return  詳細なカレンダ情報
    307         * @og.rtnNotNull
    308         */
    309        @Override
    310        public String toString() {
    311                final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE )
    312                        .append( "CLASS   : ").append( getClass().getName() ).append( CR );     // クラス名
    313
    314                @SuppressWarnings("rawtypes")
    315                final Map.Entry[] entrys = ymMap.entrySet().toArray( new Map.Entry[ymMap.size()] );
    316
    317                for( int i=0; i<entrys.length; i++ ) {
    318                        final String yyyymm = (String)entrys[i].getKey();
    319
    320                        rtn.append( yyyymm )
    321                                .append( ':' )                                          // 6.0.2.5 (2014/10/31) char を append する。
    322                                .append( (BitSet)entrys[i].getValue() )
    323                                .append( CR );
    324                }
    325
    326                return rtn.toString();
    327        }
    328}