I could give this a subtitle: "How to put a bad date in a date field", but this is not the reason of this post, just an accidental discovery. Having previously written about how to use database files in a CL program I have to mention using display files in CL.
You cannot use CL for anything as complex as a subfile. But you can for what I call "report submission screens", you know the type: a screen is presented to the user, they enter the selection criteria they desire and press Enter, and a program is submitted to batch to generate either a paper report or an email attachment. My example display file has two record formats, and is very simple:
01 A DSPSIZ(24 80 *DS3) 02 A CA03(03) *--------------------------------------------------------------------- 03 A R SCREEN1 04 A 3 3'Company. . .' 05 A COMPANY 3A B 3 16 06 A 50 ERRMSG('Invalid company number') 07 A 4 3'From date. .' 08 A FROMDATE L B 4 16DATFMT(*MDY) 09 A 51 ERRMSG('From date cannot be + 10 A greater than To date') 11 A 5 3'To date. . .' 12 A TODATE L B 5 16DATFMT(*MDY) 13 A 22 3'F3=Exit' *---------------------------------------------------------------------- 14 A R SCREEN2 15 A CA12(12) 16 A 3 3'Company. . :' 17 A COMPANY R O 3 16 18 A 4 3'From date. :' 19 A FROMDATE R O 4 16DATFMT(*MDY) 20 A FROMDATE2 8 O 4 26 21 A 5 3'To date. . :' 22 A TODATE R O 5 16DATFMT(*MDY) 23 A TODATE2 8 O 5 26 24 A 22 3'F3=Exit F12=Cancel' |
When you compare this to other examples I have given of display files notice that there isn't an Indicator area keyword, INDARA, this is because CL does not support indicator data structures.
I also have to give my function keys, lines 2 and 15, indicators as CL does not allow the "old fashioned" *INKx indicators.
Lines 3 – 13: My first record format, SCREEN1, has three fields where I can input data. FROMDATE and TODATE are date fields, this means that the fields themselves will ensure that a valid date has to be entered. I want them in *MDY format as I am in the USA, if you are in other parts of the world that uses a different date format then you'll want to use that instead. I also have two error messages, ERRMSG, on lines 6 and 9-10.
Lines 14 – 24: The second record format, SCREEN2, is much simpler as it just has five output fields based. Notice that I have date fields on lines 19 and 22 and alphanumeric equivalents on lines 20 and 23. This screen also has F12 enabled.
The CL program is also very simple:
01 PGM 02 DCL VAR(&LOOP) TYPE(*LGL) VALUE('1') 03 DCLF FILE(TESTDSPF) 04 DOWHILE COND(&LOOP) 05 SNDRCVF RCDFMT(SCREEN1) 06 IF COND(&IN03) THEN(RETURN) 07 IF COND(&COMPANY = ' ') THEN(DO) 08 CHGVAR VAR(&IN50) VALUE('1') 09 ITERATE 10 ENDDO 11 ELSE CMD(CHGVAR VAR(&IN50) VALUE('0')) 12 CVTDAT DATE(&FROMDATE) TOVAR(&FROMDATE) + FROMFMT(*MDY) TOFMT(*YYMD) TOSEP(*NONE) 13 CVTDAT DATE(&TODATE) TOVAR(&TODATE) + FROMFMT(*MDY) TOFMT(*YYMD) TOSEP(*NONE) 14 IF COND(&FROMDATE > &TODATE) THEN(DO) 15 CHGVAR VAR(&IN51) VALUE('1') 16 ITERATE 17 ENDDO 18 ELSE CMD(CHGVAR VAR(&IN51) VALUE('0')) 19 CHGVAR VAR(&FROMDATE2) VALUE(&FROMDATE) 20 CHGVAR VAR(&TODATE2) VALUE(&TODATE) 21 DOWHILE COND(&LOOP) 22 SNDRCVF RCDFMT(SCREEN2) 23 IF COND(&IN03) THEN(RETURN) 24 IF COND(&IN12) THEN(LEAVE) 25 ENDDO 26 ENDDO 27 ENDPGM |
Line 3: As a display file is a file it needs to be declared with a Declare file command, DCLF.
Line 4: As I am using modern CL there are no GOTO commands. The Do loop will execute while the variable &LOOP is on, in other words forever. This Do loop ends on line 26.
Line 5: When I was dealing with database file I would RCVF to get the data from them. With a display file I need to do a write followed by a read. The Send Receive command, SNDRCVF, does that just like the EXFMT operation code does in RPG.
Line 6: If F3 was pressed indicator 3, &IN03, will be on and want to quit the program.
Lines 7 – 10: Here I am validating that the company code, &COMPANY, entered is not blank (well, I did say this was a very simple program). If it is indicator 50, &IN50, is set on, and this will cause the error message for the company field on the screen to display an error message. The ITERATE command is just like RPG's ITER operation code, which will cause the processing to go to the top of the Do loop.
Line 11: If the company code is not blank then indicator 50 is set off.
I need to compare the dates to ensure that the from date is not greater than the to date. As the date fields are defined as dates you would think it would be easy. But CL does not support date fields, it converts them to character. The section below is copied from the compile listing showing that &FROMDATE and &TODATE date fields from the display file are handled as character in the CL program.
Declared Variables Name Type Length &COMPANY *CHAR 3 &FROMDATE *CHAR 8 &FROMDATE2 *CHAR 8 &IN03 *LGL 1 &IN12 *LGL 1 &IN50 *LGL 1 &IN51 *LGL 1 &LOOP *LGL 1 &TODATE *CHAR 8 &TODATE2 *CHAR 8 |
I cannot safely compare two character dates if they are in *MDY format, I need to convert them to be in YYYYMMDD. The first thought is: "That's *ISO format!" Alas, in CL an *ISO date must contain its date separators, YYYY-MM-DD. Fortunately there is a similar date format *YYMD which will give me what I desire without date separators.
Lines 12 and 13: I am converting the character values in &FROMDATE and &TODATE from *MDY to *YYMD using the Convert Date command, CVTDAT.
Lines 14 – 17: If the from date is greater than the to date indicator 51 is set on, line 15, and an iterate is performed, line 16, to take processing back to the top of the Do loop.
Line 18: If there is date range is OK then indicator 51 is set off.
Lines 19 and 20: I am moving the reformatted dates from the character date fields to character fields. I am doing this to show you something on the second screen.
Line 21: I have a second Do loop here to "contain" the display of the second screen. This loop ends on line 25.
Line 22: The second record format is displayed.
Line 23: If F3, &IN03, is press the program quits.
Line 24: In the display I assigned indicator 12 for the F12 key. When F12 is pressed, &IN12 is on and the logic quits this do loop, but remains within the first one.
So what does this look like when I run this program. I enter the following into the fields on the first screen.
Company. . . 001 From date. . 010101 To date. . . 123117 F3=Exit |
When I press Enter the second screen is displayed:
Company. . : 001 From date. : 20/10/01 20010101 To date. . : 20/71/31 20171231 F3=Exit F12=Cancel |
Look at those dates, there are not valid! Remember that these date fields are in *MDY format. There is no month "20", and in the To date there cannot be "71" days in any month. It would appear that the date fields in the display file have been overridden with bad dates as the program considers them as character fields. So be careful in your own programs to prevent this from happening in a non-test program.
In the above example the display contains two record format. If the display file contained only one then I can just use the SNDRCVF with no record format name and the program assumes the first, and only, record format name from the display file:
DCLF FILE(TESTDSPF) SNDRCVF |
You can learn more about this from the IBM website:
This article was written for IBM i 7.3, and should work for earlier releases too.
Hello Mr. Hutchinson.
ReplyDeleteI was reading your post, that's so good, however, I detected a mistake after this part "When I press Enter the second screen is displayed:"
In the screen or picture you have this information deploy:
"To date. . : 20/71/31 20171231"
And I think that your first date is wrong.
A part of that, I do not have any other comment.
Thanks for sharing this form to display a screen in a CL program.
Regards.
It may look like a typo, but it is really what happens.
DeleteI have to admit I was shocked when it happened.
That's another AS400's mystery.
DeleteCL is great for the simple display files. One suggestion to simplify the error handling is to add the ERRSFL keyword to the DDS at the file level and change the error test logic to:
ReplyDeleteIF COND(&IN03) THEN(RETURN)
CHGVAR VAR(&IN50) VALUE(&COMPANY = ' ')
CVTDAT ...
CVTDAT ...
CHGVAR VAR(&IN51) VALUE(&FROMDATE > &TODATE)
IF COND(&IN50 *OR &IN51) THEN(ITERATE)
CHGVAR VAR(&FROMDATE2) VALUE(&FROMDATE)
...
That allows all of the errors to show on the screen at once instead of one at a time and the reset key does not have to be pressed to clear the error(s).
You are right. It would be better in a real program, rather than an example, to do it the way you suggest. And yes I do use the ERRSFL, and written about it too here.
DeleteWell, you can have your slashes or you can have your century but not both. You needed 2 more statements to convert the dates back to the proper format as defined by the DDS.
ReplyDeleteCVTDAT DATE(&FROMDATE2) TOVAR(&FROMDATE) +
FROMFMT(*YYMD) TOFMT(*MDY) TOSEP(/)
CVTDAT DATE(&TODATE2) TOVAR(&TODATE) FROMFMT(*YYMD) +
TOFMT(*MDY) TOSEP(/)
You corrupted your date fields as defined on your screen as *MDY when your CL program converted them to *YYMD.
Myself, I would not have modified the screen fields for comparison. I would have used the work fields &FROMDATE2 and &TODATE2.
CVTDAT DATE(&FROMDATE) TOVAR(&FROMDATE2) +
FROMFMT(*MDY) TOFMT(*YYMD) TOSEP(*NONE)
CVTDAT DATE(&TODATE) TOVAR(&TODATE2) FROMFMT(*MDY) +
TOFMT(*YYMD) TOSEP(*NONE)
IF COND(&FROMDATE2 > &TODATE2) THEN(DO)
Interesting problem though.