Tools

43. 日付を加算する ADDDAT

CVTDAT (日付形式変換) は以前にも紹介したように非常に役に立つコマンドであるが
実際の運用面となると日付の加算または減算機能も欲しいところである。

納期がつねに 14日間とわかっているならば今日、発注すると納入予定日はいつになるのだろうか ?
それを算出したい、という日付計算の用途は数多くあるはずである。

ここで紹介する ADDDAT (日付の加算)コマンドは日付に加算または減算を行うことができるコマンドである。
ADDDATCVTDAT コマンドの高度な使い方を学習できるのと同時に
*JUL (JULIAN DATE)の意味も学習することができるようになっている。
実は ADDDATQUSRTOOL の中に紹介されているコマンドであるが、
ここではそれをもっとわかりやすく肝心なところにスポットを当てて解説するようにした。

ADDDAT の処理が理解できれば
ユーザーはより高度な日付の処理プログラムも自作することができるはずである。

本当は CLPやRPGの組み込み関数として ADDDAT が提供されていてもおかしくはない。
それほど ADDDAT は適用業務では重要なのである。

加算または減算日数 日数を指定する。
減算日数の場合は -15 のように前にマイナス符号をつけて指定する。
結果の日付 結果の日付を受け取るための文字変数(6桁または8桁の文字)を指定する。
開始の日付 (SYS FMT) 演算元となる日付の変数または具体的な日付を6桁または8桁の数値で指定する。
*TODAY は本日の日付を意味する。
結果の日付の様式 結果の日付の日付形式を指定する。
西暦20YYMMDD の場合は *YYMD と指定する。
日付の様式 開始の日付の日付形式を指定する。
結果の日付その2 未使用
【コマンド: ADDDAT】
0001.00 /* TAADATA - ADD DATE - 日付加算                                   */   
0002.00 /*PARMS ALLOW((*IPGM)(*BPGM)) PGM(TAADATAC) PRDLIB(TAATOOL)        */   
0003.00 /*                                                                 */   
0004.00 /*  ADD DATE コマンドは                                            */   
0005.00 /*  日付の加算または減算の結果を戻します。                         */   
0006.00 /*                                                                 */   
0007.00 /* TAA CPP IS TAADATAC                                             */   
0008.00 /*                                                                 */   
0009.00              CMD        PROMPT(' 日付の加算 ')                          
0010.00              PARM       KWD(DAYS) TYPE(*DEC) LEN(5) RANGE(-35000 +      
0011.00                           35000) MIN(1) PROMPT(' 加算または減算日数 ')  
0012.00              PARM       KWD(TOVAR) TYPE(*CHAR) LEN(6) RTNVAL(*YES) +    
0013.00                           PROMPT(' 結果の日付 ')                        
0014.00              PARM       KWD(DATE) TYPE(*DEC) LEN(6 0) DFT(*TODAY) +     
0015.00                           RANGE(000000 999999) SPCVAL((*TODAY 0) +      
0016.00                           (*JOB -1)) PROMPT(' 開始の日付 (SYS FMT)')    
0017.00              PARM       KWD(TOVARFMT) TYPE(*CHAR) LEN(7) RSTD(*YES) +   
0018.00                           DFT(*JOB) VALUES('*SYSVAL' '*MDY' '*DMY' +    
0019.00                           '*YMD' '*JUL' '*JOB' '*CYMD' '*MDYY' +        
0020.00                           '*DMYY' '*YYMD' '*ISO' '*USA' '*EUR' +        
0021.00                           '*JIS') PROMPT(' 結果の日付の様式 ')        
0022.00              PARM       KWD(DATEFMT) TYPE(*CHAR) LEN(7) RSTD(*YES) +  
0023.00                           DFT(*JOB) VALUES('*SYSVAL' '*MDY' '*DMY' +  
0024.00                           '*YMD' '*JUL' '*JOB') PROMPT(' 日付の様式 ')
0025.00              PARM       KWD(TOVAR2) TYPE(*CHAR) LEN(10) RTNVAL(*YES) +
0026.00                           PROMPT(' 結果の日付その2 ')
【コンパイル】

値の受け取りが必要なのでバッチ処理環境のみの実行を次のように指定してコンパイルする。

CRTCMD CMD(MYLIB/ADDDAT) PGM(MYLIB/ADDDATCL) SRCFILE(MYSRCLIB/QCMDSRC) ALOW(*IPGM *BPGM) AUT(*ALL)
【CLP: ADDDATCL】
0001.00              PGM        PARM(&DAYS &TOVAR &DATE &TOVARFMT &DATEFMT +    
0002.00                           &TOVAR2)                                      
0003.00 /*-------------------------------------------------------------------*/ 
0004.00 /*   ADDDATCL  :   日付の加算                                        */ 
0005.00 /*  TAADATAC - add or sub N days from a given date - CPP for ADDDAT  */ 
0006.00 /*                                                                   */ 
0007.00 /*   2015/11/02  修正                                                */ 
0008.00 /*    動作原理は *JUL 形式の日付に変換してから加算または減算を       */ 
0009.00 /*    行ってから元の日付形式に CVTDAT で戻すことである。             */ 
0010.00 /*    *JUL 形式 = 年号 (2 桁 )+ 年初からの通算日 (3 桁 ) を          */ 
0011.00 /*    理解しておけば処理の内容を理解することができる。               */ 
0012.00 /*-------------------------------------------------------------------*/ 
0013.00              DCL        &DAYS *DEC LEN(5 0)                             
0014.00              DCL        &TOVAR *CHAR LEN(6)                             
0015.00              DCL        &TOVAR2 *CHAR LEN(10)                           
0016.00              DCL        &DATE *DEC LEN(6 0)                             
0017.00              DCL        &TOVARFMT *CHAR LEN(7)                          
0018.00              DCL        &DATEFMT *CHAR LEN(7)                           
0019.00              DCL        &WRKDAT *CHAR LEN(6)                            
0020.00              DCL        &WRKDAT2 *CHAR LEN(10)                          
0021.00              DCL        &WRKDAT5 *CHAR LEN(5)                           
0022.00              DCL        &JULIANA *CHAR LEN(5)                           
0023.00              DCL        &YRD *DEC LEN(2 0)                              
0024.00              DCL        &DAYSD *DEC LEN(3 0)                            
0025.00              DCL        &LEAP *DEC LEN(2 0)                             
0026.00              DCL        &DAYSINYEAR *DEC LEN(3 0)                       
0027.00              DCL        &NUM5 *DEC LEN(5)                               
0028.00              DCL        &NUM2 *DEC LEN(2)                               
0029.00              DCL        &NOTOVAR *CHAR LEN(1)                           
0030.00              DCL        &NOTOVAR2 *CHAR LEN(1)                          
0031.00              DCL        &ERRORSW *LGL           /* Standard error */    
0032.00              DCL        &MSGID *CHAR LEN(7)     /* Standard error */    
0033.00              DCL        &MSG *CHAR LEN(512)     /* Standard error */    
0034.00              DCL        &MSGDTA *CHAR LEN(512)  /* Standard error */    
0035.00              DCL        &MSGF *CHAR LEN(10)     /* Standard error */    
0036.00              DCL        &MSGFLIB *CHAR LEN(10)  /* Standard error */    
0037.00              DCL        &KEYVAR *CHAR LEN(4)    /* Standard error */    
0038.00              DCL        &KEYVAR2 *CHAR LEN(4)   /* Standard error */    
0039.00              DCL        &RTNTYPE *CHAR LEN(2)   /* Standard error */    
0040.00              MONMSG     MSGID(CPF0000) EXEC(GOTO STDERR1) /* Std err */ 
0041.00                         /* Check if requested format agrees with */     
0042.00                         /*   the TODAT variable that has been    */     
0043.00                         /*   been specified                      */     
0044.00                         /* Invalid to use new format and 6 char  */     
0045.00                         /*   return variable                     */     
0046.00              IF         ((&TOVARFMT *EQ '*MDYY') *OR +                  
0047.00                           (&TOVARFMT *EQ '*DMYY') *OR +                 
0048.00                           (&TOVARFMT *EQ '*YYMD') *OR +                 
0049.00                           (&TOVARFMT *EQ '*ISO') *OR +                  
0050.00                           (&TOVARFMT *EQ '*USA') *OR +                  
0051.00                           (&TOVARFMT *EQ '*EUR') *OR +                  
0052.00                           (&TOVARFMT *EQ '*CYMD') *OR +                 
0053.00                           (&TOVARFMT *EQ '*JIS')) DO /* New fmt */      
0054.00              CHGVAR     &TOVAR2 '  '                                    
0055.00              MONMSG     MSGID(MCH3601) EXEC(DO) /* No variable spec */  
0056.00              SNDPGMMSG  MSGID(CPF9898) MSGF(QCPFMSG) MSGTYPE(*ESCAPE) + 
0057.00                           MSGDTA('If one of the new date formats +      
0058.00                           is used for TOVARFMT, you cannot use +        
0059.00                           a return variable for TOVAR. You must +       
0060.00                           use a return variable for TOVAR2')            
0061.00              ENDDO      /* No variable specified */                     
0062.00              ENDDO      /* New fmt */                                   
0063.00                         /* Check to see that at least one return */    
0064.00                         /*   variable is specified */                  
0065.00              CHGVAR     &TOVAR ' '                                     
0066.00              MONMSG     MSGID(MCH3601) EXEC(CHGVAR &NOTOVAR 'X')       
0067.00              CHGVAR     &TOVAR2 ' '                                    
0068.00              MONMSG     MSGID(MCH3601) EXEC(CHGVAR &NOTOVAR2 'X')      
0069.00              IF         ((&NOTOVAR *EQ 'X') *AND +                     
0070.00                           (&NOTOVAR2 *EQ 'X')) DO /* Both missing */   
0071.00              SNDPGMMSG  MSGID(CPF9898) MSGF(QCPFMSG) MSGTYPE(*ESCAPE) +
0072.00                           MSGDTA('You must specify a return +          
0073.00                           variable for either TOVAR or TOVAR2')        
0074.00              ENDDO      /* Both missing */                             
0075.00                         /* &DATE=0 is special value *TODAY */          
0076.00              IF         (&DATE *EQ 0) DO /* Get todays date */         
0077.00              RTVSYSVAL  SYSVAL(QDATE) RTNVAR(&WRKDAT)                  
0078.00              GOTO       CVTDAT                                         
0079.00              ENDDO      /* Get todays date */                          
0080.00              IF         (&DATE *EQ -1) DO /* Get job date */           
0081.00              RTVJOBA    DATE(&WRKDAT)                                  
0082.00              GOTO       CVTDAT                                         
0083.00              ENDDO      /* Get job date */                             
0084.00                         /* Use the date from the command */             
0085.00              CHGVAR     &WRKDAT &DATE                                   
0086.00              IF         (&DATEFMT *NE '*JUL') DO /* Not julian input */ 
0087.00  CVTDAT:     CVTDAT     DATE(&WRKDAT) TOVAR(&JULIANA) +                 
0088.00                           FROMFMT(&DATEFMT) TOFMT(*JUL) +               
0089.00                           TOSEP(*NONE) /* Convert to Julian */          
0090.00              MONMSG     MSGID(CPF0555) EXEC(SNDPGMMSG +                 
0091.00                         MSGID(CPF9898) MSGF(QCPFMSG) MSGTYPE(*ESCAPE) + 
0092.00                         MSGDTA('DATE parameter value cannot +           
0093.00                         be converted'))                                 
0094.00              ENDDO      /* Not julian input */                          
0095.00              IF         (&DATEFMT *EQ '*JUL') DO /* Julian input */     
0096.00              CHGVAR     &WRKDAT5 %SST(&WRKDAT 2 5)                      
0097.00              CVTDAT     DATE(&WRKDAT5) TOVAR(&JULIANA) +                
0098.00                           FROMFMT(&DATEFMT) TOFMT(*JUL) +               
0099.00                           TOSEP(*NONE) /* Convert to Julian */          
0100.00              MONMSG     MSGID(CPF0555) EXEC(SNDPGMMSG +                 
0101.00                         MSGID(CPF9898) MSGF(QCPFMSG) MSGTYPE(*ESCAPE) + 
0102.00                         MSGDTA('DATE parameter value cannot +           
0103.00                         be converted'))                                 
0104.00              ENDDO      /* Julian input */                              
0105.00                         /* Substring for year and day */                
0106.00                                                                         
0107.00  /* *JULIANA から先頭 2 桁の年号と 3 桁の通算日を取り出す */            
0108.00              CHGVAR     &YRD %SST(&JULIANA 1 2)                         
0109.00              CHGVAR     &DAYSD %SST(&JULIANA 3 3)                       
0110.00 /* 通算日に加算または減算を行う */                                      
0111.00              CHGVAR     VAR(&NUM5) VALUE(&DAYSD + &DAYS) /* Add days */ 
0112.00                                                                         
0113.00 /* 以下の演算は通常の日付形式に戻すための汎用的な演算である */          
0114.00              /* 日数部分がマイナスなら年から減算する */                 
0115.00  CHKPLUS:    IF         (&NUM5 *GT 0) GOTO CHKLEAP /* If positive */    
0116.00              IF         (&YRD *EQ 00) CHGVAR &YRD 99 /* Year 2000 */    
0117.00              ELSE       CHGVAR &YRD (&YRD -1) /* Decrement year */      
0118.00  CHKLEAP:    CHGVAR     &NUM2 (&YRD / 4) /* Chk leap year */            
0119.00              CHGVAR     &LEAP (&YRD - (&NUM2 * 4))                      
0120.00              /* LEAP は年号を 4 で割った余り */                         
0121.00              IF         (&LEAP *GT 0) CHGVAR &DAYSINYEAR 365            
0122.00              ELSE       CHGVAR &DAYSINYEAR 366 /* Leap year */          
0123.00              /* DAYINYEAR は年間の日数  */                              
0124.00              /* 日数部分がマイナスか ? */                               
0125.00              IF         (&NUM5 *LE 0) DO /* Days are negative */        
126.00              CHGVAR     &NUM5 (&NUM5 + &DAYSINYEAR)                     
127.00              GOTO       CHKPLUS /* Check for positive days */           
128.00              ENDDO      /* End negative days */                         
129.00              /* 日数部分が年間日数をオーバーするか ? */                 
130.00              IF         (&NUM5 *GT &DAYSINYEAR) DO /* Ovfl */           
131.00              IF         (&YRD *EQ 99) CHGVAR &YRD -1 /* Year 2000 */    
132.00              CHGVAR     &YRD (&YRD + 1)  /* Bump year */                
133.00              CHGVAR     &NUM5 (&NUM5 - &DAYSINYEAR) /* Subtract */      
134.00              GOTO       CHKLEAP /* Test for next year */                
135.00              ENDDO      /* End days greater than days-in-year */        
136.00                                                                         
137.00              /* 正常な *JUL になったところで元の日付形式に戻す */       
138.00              CHGVAR     &DAYSD &NUM5  /* Chg to 3 digits */             
139.00                              /* Substring back into Julian date */      
140.00              CHGVAR     %SST(&JULIANA 1 2) &YRD                         
141.00              CHGVAR     %SST(&JULIANA 3 3) &DAYSD                       
142.00                         /* Convert to 8 character format */             
143.00              CVTDAT     DATE(&JULIANA) TOVAR(&WRKDAT2) FROMFMT(*JUL) +  
144.00                           TOFMT(&TOVARFMT) TOSEP(*NONE) /* Convert +    
145.00                           to sys fmt */                                 
146.00                         /* If last 2 postions are blank, use 6      */  
0147.00                         /*   character format */                        
0148.00              IF         (%SST(&WRKDAT2 7 4) *EQ ' ') DO /* 6 char */    
0149.00              CHGVAR     &TOVAR &WRKDAT2                                 
0150.00              MONMSG     MSGID(MCH3601) /* Ignore if not specified */    
0151.00              ENDDO      /* 6 char */                                    
0152.00                         /* Always move to the 10 char value */          
0153.00              CHGVAR     &TOVAR2 &WRKDAT2                                
0154.00              MONMSG     MSGID(MCH3601) /* Ignore if not specified */    
0155.00                         /* Clear all messages */                        
0156.00              RMVMSG     CLEAR(*ALL)                                     
0157.00              RETURN     /* Normal end of program */                     
0158.00  STDERR1:               /* Standard error handling routine */           
0159.00              IF         &ERRORSW SNDPGMMSG MSGID(CPF9999) +             
0160.00                           MSGF(QCPFMSG) MSGTYPE(*ESCAPE)                
0161.00              CHGVAR     &ERRORSW '1' /* Set to fail on error */         
0162.00              RCVMSG     MSGTYPE(*EXCP) RMV(*NO) KEYVAR(&KEYVAR)         
0163.00  STDERR2:    RCVMSG     MSGTYPE(*PRV) MSGKEY(&KEYVAR) RMV(*NO) +        
0164.00                           KEYVAR(&KEYVAR2) MSG(&MSG) +                  
0165.00                           MSGDTA(&MSGDTA) MSGID(&MSGID) +               
0166.00                           RTNTYPE(&RTNTYPE) MSGF(&MSGF) +               
0167.00                           SNDMSGFLIB(&MSGFLIB)                          
0168.00              IF         (&RTNTYPE *NE '02') GOTO STDERR3             
0169.00              IF         (&MSGID *NE ' ') SNDPGMMSG +                 
0170.00                           MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) +       
0171.00                           MSGDTA(&MSGDTA) MSGTYPE(*DIAG)             
0172.00              IF         (&MSGID *EQ ' ') SNDPGMMSG +                 
0173.00                           MSG(&MSG) MSGTYPE(*DIAG)                   
0174.00              RMVMSG     MSGKEY(&KEYVAR2)                             
0175.00  STDERR3:    RCVMSG     MSGKEY(&KEYVAR) MSGDTA(&MSGDTA) +            
0176.00                           MSGID(&MSGID) MSGF(&MSGF) +                
0177.00                           SNDMSGFLIB(&MSGFLIB)                       
0178.00              SNDPGMMSG  MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) +         
0179.00                           MSGDTA(&MSGDTA) MSGTYPE(*ESCAPE)           
0180.00              ENDPGM
【コンパイル】
CRTCLPGM PGM(MYLIB/ADDDATCL) SRCFILE(MYSRCLIB/QCLSRC) AUT(*ALL)
【解説】

このCLPの理解するためのコアは *JUL の日付形式の理解である。
*JUL 形式の日付とは

2桁年号 + 3桁の年間通算日 = 5桁の *JUL形式

となっていることである。
例えば 2016年 1月 1日は年度の始まりの日であるので *JUL 形式に直すと

2016年 1月 1日 = 16001

となる。
これが理解できれば

2015/12/31 は 15365

であることがわかるだろう。
しかし 2016年はうるう年であるので

2016/12/31 は 16366

となる。

ところで 2015/12/31 に +1 すると *JUL ; 15365 の下3桁の通算日にだけ+1加算して 366 となるが
これは 2015年の年間日数(&DAYSINYEAR=265)を超えるので
365 を引いてかつ年に +1 して*JUL は 16001 となる。
この *JUL を元の日付形式に CVTDAT で戻すと 2016/01/01 となるわけである。
このように *JUL に変換して計算してからまたもとの日付形式に戻す、ということを計算している。
これが ADDDAT の動作原理である。

これが理解できれば自社のカレンダーや日祝日、連休を考慮した日付の計算もできるはずである。
カレンダーはすでにwebサービスとしてインターネットで公開されているので
それを利用してさらに一般化したADDDAT をWebサービスとして公開することができるかもしれない。

一般化された日付計算が公開されれば、どのようなプログラムからでも利用することができる。

【参考サイト】

※ ADDDAT コマンドは SPOOLライターや EnterpriseServer にも組み込まれているので
ユーザーであれば使用することができる。