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