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.system;                                                            // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
017
018// import java.io.FileInputStream;                                                                      // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
019// import java.io.FileOutputStream;                                                                     // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
020import java.io.BufferedInputStream;                                                                     // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
021import java.io.BufferedOutputStream;                                                            // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
022import java.io.InputStream;                                                                                     // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
023import java.io.OutputStream;                                                                            // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
024import java.nio.file.Paths;                                                                                     // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
025import java.nio.file.Files;                                                                                     // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
026
027import java.util.Date;
028import java.util.Locale;
029// import java.util.Calendar;                                                                           // 7.0.1.4 (2018/11/26)
030import java.util.concurrent.ConcurrentMap;                                                      // 7.0.1.3 (2018/11/12)
031import java.util.concurrent.ConcurrentHashMap;                                          // 7.0.1.3 (2018/11/12)
032import java.text.DateFormat;
033import java.text.SimpleDateFormat;
034
035// import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;   // 6.1.0.0 (2014/12/26) refactoring
036
037/**
038 * DateSet.java は、入力ファイルの日付、時刻キーワードを実行時の日時で変換して出力します。
039 *
040 * 変換には、$(yyyy)の形式で指定し、カッコの文字列は java.text.SimpleDateFormat で使用する
041 * 時刻フォーマット構文を用います。
042 * また、引数に keys,vals を渡すことで、$(KEY1) 文字列を VAL1 文字列と置き換えます。
043 *
044 *  サンプルファイル
045 *  $(yyyy/MM/dd)        年/月/日を表します。
046 *  $(yy)                年だけを2桁で表します。
047 *  $(MM)                月を2桁 (02,03など)で表します。
048 *  $(dd)                日を2桁 (02,03など)で表します。
049 *  $(HH:mm:ss)          時:分:秒を表します。
050 *  $(MMMMMMMM)          月をフルスペルで表します。
051 *  $(MMM)               月を3桁固定(Mar,Aplなど)で表します。
052 *  $(EEEEEEEE)          曜日をフルスペルで表します。
053 *  $(EEE)               曜日を3桁固定(Sun,Monなど)で表します。
054 *
055 *  // 7.0.1.3 (2018/11/12)                                                      2019/01/01    2030/01/01
056 *  $(ATIME)             通算秒数( new Date().getTime()/1000 )  の 10桁文字列    1546268400    1893423600
057 *  $(BTIME)             通算分数( new Date().getTime()/60000 ) の  8桁文字列      25771140      31557060
058 *    ※ BTIME が桁あふれするのは、2160/02/18 です。
059 *
060 *   時刻フォーマット構文
061 *
062 *   記号     意味                    表示                例
063 *   ------   -------                 ------------        -------
064 *   G        年号                    (テキスト)          AD
065 *   y        年                      (数値)              1996
066 *   M        月                      (テキスト & 数値)  July & 07
067 *   d        日                      (数値)              10
068 *   h        午前/午後の時 (1~12)    (数値)              12
069 *   H        一日における時 (0~23)   (数値)              0
070 *   m        分                      (数値)              30
071 *   s        秒                      (数値)              55
072 *   S        ミリ秒                  (数値)              978
073 *   E        曜日                    (テキスト)          火曜日
074 *   D        年における日            (数値)              189
075 *   F        月における曜日          (数値)              2 (7月の第2水曜日)
076 *   w        年における週            (数値)              27
077 *   W        月における週            (数値)              2
078 *   a        午前/午後               (テキスト)          PM
079 *   k        一日における時 (1~24)   (数値)              24
080 *   K        午前/午後の時 (0~11)    (数値)              0
081 *   z        時間帯                  (テキスト)          PDT
082 *   '        テキスト用エスケープ
083 *   ''       単一引用符                                  '
084 *
085 *  パターン文字のカウントによって、そのフォーマットが決まります。
086 *  (テキスト): 4以上: フル形式を使用します。4以下: 短いまたは省力された形式があれば、それを使用します。
087 *
088 *  (数値): 最小桁数。これより短い数値は、この桁数までゼロが追加されます。年には特別な処理があります。
089 *  つまり、'y'のカウントが2なら、年は2桁に短縮されます。
090 *
091 *  (テキスト & 数値): 3以上ならテキストを、それ以外なら数値を使用します。
092 *
093 *  パターンの文字が['a'..'z']と['A'..'Z']の範囲になければ、その文字は引用テキストとして扱われます。
094 *  たとえば、':'、'.'、' '、'#'、'@'などの文字は、単一引用符に囲まれていなくても、
095 *  結果の時刻テキストに使用されます。
096 *
097 *  無効なパターン文字がパターンに入っていると、フォーマットや解析で例外がスローされます。
098 *
099 *  USロケールを使った例:
100 *
101 *   フォーマットパターン                   結果
102 *   --------------------                   ----
103 *   "yyyy.MM.dd G 'at' hh:mm:ss z"    ⇒  1996.07.10 AD at 15:08:56 PDT
104 *   "EEE, MMM d, ''yy"                ⇒  Wed, July 10, '96
105 *   "h:mm a"                          ⇒  12:08 PM
106 *   "hh 'o''''clock' a, zzzz"         ⇒  12 o'clock PM, Pacific Daylight Time
107 *   "K:mm a, z"                       ⇒  0:00 PM, PST
108 *   "yyyyy.MMMMM.dd GGG hh:mm aaa"    ⇒  1996.July.10 AD 12:08 PM
109 *
110 * @og.rev 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
111 * @og.rev 7.0.1.3 (2018/11/12) keys,valsをMapに変更。BUILD_TYPEのビルド番号を100秒単位に変更。
112 *
113 * @version  0.9.0  1999/03/09
114 * @author   Kazuhiko Hasegawa
115 * @since    JDK1.1,
116 */
117public class DateSet {
118        private final ConcurrentMap<String,String> prmMap = new ConcurrentHashMap<>();          // 7.0.1.3 (2018/11/12)
119
120//      private String[] keys ;
121//      private String[] vals ;
122
123        /**
124         * デフォルトコンストラクター
125         *
126         * Mapの初期値を設定しておきます。
127         *
128         * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
129         * @og.rev 7.0.1.4 (2018/11/26) ATIME,BTIME 追加
130         */
131        public DateSet() {
132                prmMap.put( "ATIME" ,  Long.toString( new Date().getTime()/1_000 ) );           // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseUnderscoresInNumericLiterals
133                prmMap.put( "BTIME" ,  Long.toString( new Date().getTime()/60_000 ) );          // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseUnderscoresInNumericLiterals
134
135        //      final Calendar now = Calendar.getInstance();
136        //      final int tm = ( now.get( Calendar.HOUR_OF_DAY ) * 60 + now.get( Calendar.MINUTE ) ) / 15 ;             // 0 ~ 96未満の数値になる
137
138        //      final String CTIME = getDate( "yyDDD" ) + String.format( "%02d" , tm ) ;
139        //      prmMap.put( "CTIME" ,  CTIME );
140        }
141
142        /**
143         * フォーマット解析時に置き換える キーと値のMapを設定します。
144         *
145         * $(KEY1) 文字列を VAL1 文字列と置き換える処理を行います。これにより日付以外の
146         * 文字列を置き換える処理を実行できます。
147         *
148         * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
149         *
150         * @param       pMap    置き換え元のMap
151         */
152        public void setParamMap( final ConcurrentMap<String,String> pMap ) {
153                if( pMap != null ) {
154                         prmMap.putAll( pMap );
155                }
156        }
157
158//      /**
159//       * フォーマット解析時に置き換える キーと値の配列を設定します。
160//       *
161//       * $(KEY1) 文字列を VAL1 文字列と置き換える処理を行います。これにより日付以外の
162//       * 文字列を置き換える処理を実行できます。
163//       *
164//       * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
165//       *
166//       * @param       inkeys  置き換え元キー配列
167//       * @param       invals  置き換え元値配列
168//       */
169//      public void setKeysVals( final String[] inkeys, final String[] invals ) {
170//              if( inkeys != null && invals != null && inkeys.length == invals.length ) {
171//                      final int size = inkeys.length ;
172//                      keys = new String[size];
173//                      vals = new String[size];
174//                      System.arraycopy( inkeys,0,keys,0,size );
175//                      System.arraycopy( invals,0,vals,0,size );
176//              }
177//      }
178
179        /**
180         * 現在日付、時刻をフォーマット指定個所に埋め込みます。
181         * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
182         *
183         * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
184         * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.StringUtil → fukurou.system.HybsConst に変更
185         * @og.rev 8.5.3.2 (2023/10/13) JDK21注意。JDK17までは、Windows-31J だったが、JDK21から、UTF-8 に変更されている。
186         *
187         * @param       inByte  変換元バイト配列(可変長引数)
188         * @return      変換後のバイト配列
189         */
190        public byte[] change( final byte... inByte ) {
191                final byte[] outByte = new byte[inByte.length+100];             // 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableCouldBeFinal
192                int add = 0;
193                // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidReassigningLoopVariables
194//              for( int i=0; i<inByte.length; i++ ) {
195//                      if( inByte[i] == '$' && i<inByte.length-1 && inByte[i+1] == '(' ) {
196//                              // 8.5.4.2 (2024/01/12) PMD 7.0.0 ShortVariable j ⇒ jj に変更
197//                              int jj = 0;
198//                              while( inByte[i+jj+2] != ')') { jj++; }
199//                              final String str = changeForm( new String( inByte,i+2,jj,HybsConst.DEFAULT_CHARSET ) );         // 6.4.2.0 (2016/01/29)
200//                              final byte[] byteDate = str.getBytes( HybsConst.DEFAULT_CHARSET ) ;                                                     // 6.4.2.0 (2016/01/29)
201//                              // 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
202//                              System.arraycopy( byteDate,0,outByte,add,byteDate.length );             // 6.3.6.0 (2015/08/16)
203//                              add += byteDate.length ;                                                                                // 6.3.6.0 (2015/08/16)
204//                              i   += jj+2;
205//                      }
206//                      else {
207//                              outByte[add] = inByte[i];
208//                              add++;
209//                      }
210//              }
211                final int inLen = inByte.length;
212                int ii = 0;
213                while( ii<inLen ) {
214                        if( inByte[ii] == '$' && ii<inLen-1 && inByte[ii+1] == '(' ) {
215                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ShortVariable j ⇒ jj に変更
216                                int jj = 0;
217                                while( inByte[ii+jj+2] != ')') { jj++; }
218                                final String str = changeForm( new String( inByte,ii+2,jj,HybsConst.DEFAULT_CHARSET ) );        // 6.4.2.0 (2016/01/29)
219                                final byte[] byteDate = str.getBytes( HybsConst.DEFAULT_CHARSET ) ;                                                     // 6.4.2.0 (2016/01/29)
220                                // 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
221                                System.arraycopy( byteDate,0,outByte,add,byteDate.length );             // 6.3.6.0 (2015/08/16)
222                                add += byteDate.length ;                                                                                // 6.3.6.0 (2015/08/16)
223                                ii  += jj+2;
224                        }
225                        else {
226                                outByte[add] = inByte[ii];
227                                add++;
228                        }
229                        ii++ ;
230                }
231                final byte[] rtnByte = new byte[add];
232                System.arraycopy( outByte,0,rtnByte,0,add );
233                return rtnByte;
234        }
235
236        /**
237         * パラメータの変換、および、現在日付、時刻のフォーマット変換を行います。
238         *
239         * 先に、パラメータの変換を行います。form が、Mapのkey にマッチすれば、
240         * その値を返します。マッチしなければ、時刻のフォーマット変換を行います。
241         * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
242         *
243         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
244         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
245         * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
246         *
247         * @param       form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss" )
248         *
249         * @return      フォーマット変換結果
250         */
251        public String changeForm( final String form ) {
252                // Map#getOrDefault( key,defVal )はdefValも評価が必ず実行されるため、使えない。
253                // return prmMap.getOrDefault( form,DateSet.getDate( form ) ;
254
255                final String rtn = prmMap.get( form );
256
257//              return rtn == null ? DateSet.getDate( form ) : rtn ;
258                return rtn == null ? getDate( form ) : rtn ;    // 8.5.4.2 (2024/01/12) PMD 7.0.0 UnnecessaryFullyQualifiedName
259
260//              // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
261//              if( keys != null && vals != null ) {
262//                      for( int i=0; i<keys.length; i++ ) {
263//                              if( form.equals( keys[i] ) ) {
264//                                      return vals[i];
265//                              }
266//                      }
267//              }
268//
269//              return DateSet.getDate( form );
270        }
271
272        /**
273         * パラメータの変換、および、現在日付、時刻のフォーマット変換を行います。
274         *
275         * 先に、パラメータの変換を行います。form が、Mapのkey にマッチすれば、
276         * その値を返します。マッチしなければ、時刻のフォーマット変換を行います。
277         * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
278         *
279         * @param       form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss" )
280         *
281         * @return      フォーマット変換結果
282         * @og.rtnNotNull
283         */
284        public String changeString( final String form ) {
285                final StringBuilder buf = new StringBuilder( HybsConst.BUFFER_MIDDLE );
286                int bkst = 0;
287                int st = form.indexOf( "$(" );
288                while( st >= 0 ) {
289                        buf.append( form.substring( bkst,st ) );
290                        final int ed = form.indexOf( ')',st+2 );                                // 6.0.2.5 (2014/10/31) refactoring
291                        buf.append( changeForm( form.substring( st+2,ed ) ) );
292                        bkst = ed + 1;
293                        st = form.indexOf( "$(",bkst );
294                }
295                buf.append( form.substring( bkst ) );
296
297                return buf.toString();
298        }
299
300        /**
301         * 現在日付、時刻を指定のフォーマットで文字列に変換して返します。
302         * 出力フォーマットは、"yyyy/MM/dd HH:mm:ss" 固定です。
303         *
304         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
305         * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
306         *
307         * @return      現在日付、時刻 ( 例 2012/09/05 18:10:24 )
308         * @og.rtnNotNull
309         */
310        public static String getDate() {
311                final DateFormat formatter = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss",Locale.JAPAN );
312                return formatter.format( new Date() );
313        }
314
315        /**
316         * 現在時刻を指定のフォーマットで文字列に変換して返します。
317         * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
318         * 変換時のロケーションは、Locale.JAPAN です。
319         * 現在時刻は、new Date() で求めます。
320         *
321         * @param       form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss.SSS" )
322         *
323         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
324         * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
325         *
326         * @return      現在日付、時刻
327         * @og.rtnNotNull
328         * @see         java.text.SimpleDateFormat
329         */
330        public static String getDate( final String form ) {
331                final DateFormat formatter = new SimpleDateFormat( form,Locale.JAPAN );
332                return formatter.format( new Date() );
333        }
334
335        /**
336         * 指定時刻を指定のフォーマットで文字列に変換して返します。
337         * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
338         * 変換時のロケーションは、Locale.JAPAN です。
339         * 指定時刻は、new Date( time ) で求めます。
340         *
341         * @param       time 指定のカレントタイムのロング値
342         * @param       form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss.SSS" )
343         *
344         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
345         * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
346         *
347         * @return      現在日付、時刻( 例 2001/04/17 15:48:22 )
348         * @og.rtnNotNull
349         */
350        public static String getDate( final long time,final String form ) {
351                final DateFormat formatter = new SimpleDateFormat( form,Locale.JAPAN );
352                return formatter.format( new Date( time ) );
353        }
354
355        /**
356         * 入力ファイルの時刻フォーマットを変換して出力ファイルに書き込みます。
357         *
358         * 引数に &lt;key1&gt; &lt;val1&gt; のペア情報を渡すことが可能です。
359         * 先に、keys,vals の変換を行います。form が、keys にマッチすれば、vals を
360         * 返します。最後までマッチしなければ、時刻のフォーマット変換を行います。
361         * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
362         * フォーム文字列例 (  "yyyy/MM/dd HH:mm:ss" )
363         *
364         * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
365         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
366         *
367         * @param       args 引数配列( 入力ファイル 出力ファイル キー1 値1 ・・・
368         * @throws Throwable なんらかのエラーが発生した場合。
369         */
370        public static void main( final String[] args ) throws Throwable {
371                if( args.length > 2 && ( args.length % 2 != 0 ) ) {
372                        System.err.println( "Usage: java org.opengion.fukurou.system.DateSet <inputFile> <outputFile> [<key1> <val1> ・・・]" );
373                        return ;
374                }
375
376                final ConcurrentMap<String,String> prmMap = new ConcurrentHashMap<>();          // 7.0.1.3 (2018/11/12)
377
378                for( int i=2; i<args.length; i+=2 ) {
379                        prmMap.put( args[i] , args[i+1] );
380                }
381
382//              String[] keys = new String[ (args.length-2)/2 ];
383//              String[] vals = new String[ (args.length-2)/2 ];
384//              for( int i=1; i<=keys.length; i++ ) {
385//                      keys[i-1] = args[i*2];
386//                      vals[i-1] = args[i*2+1];
387//              }
388
389                // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
390//              final FileInputStream filein = new FileInputStream( args[0] );
391                byte[] byteIn = null;
392                // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
393//              try ( FileInputStream filein = new FileInputStream( args[0] ) ) {
394                try ( InputStream filein = new BufferedInputStream( Files.newInputStream(Paths.get(args[0])) ) ) {
395//                      final byte[] byteIn = new byte[ filein.available() ];
396                        byteIn = new byte[ filein.available() ];
397                        final int len = filein.read( byteIn );
398                        if( len != byteIn.length ) {
399                                final String errMsg = "読み取りファイルのデータが切り捨てられました。" +
400                                                                "File=" + args[0] + " Length=" + len  + " Input=" + byteIn.length ;
401                                System.err.println( errMsg );
402                        }
403                }
404//              filein.close();
405
406                if( byteIn != null ) {
407                        final DateSet dateSet = new DateSet();
408        //              dateSet.setKeysVals( keys,vals );
409                        dateSet.setParamMap( prmMap );
410                        final byte[] byteout = dateSet.change( byteIn );
411
412                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
413        //              final FileOutputStream fileout = new FileOutputStream( args[1] );
414        //              fileout.write( byteout );
415        //              fileout.close();
416                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidFileStream 対応
417//                      try ( FileOutputStream fileout = new FileOutputStream( args[1] ) ) {
418                        try ( OutputStream fileout = new BufferedOutputStream( Files.newOutputStream(Paths.get(args[1])) ) ) {
419                                fileout.write( byteout );
420                        }
421                }
422        }
423}