Date-Handling in CL Procedures
January 19, 2005 Ted Holt
The code for this article is available for download.
A common saying is, “It’s the little things that get you.” The maxim was certainly true recently in one iSeries shop. I may not have all the details exactly right, but it seems nobody was able to pry any information out of the computer. Investigation revealed an unanswered message to QSYSOPR and a queue of jobs eagerly waiting for a chance to run. Apparently, the unanswered message owed its existence to the fact that some human being had entered an invalid date value into a prompt screen.
My impression is that this problem arose because of a combination of sloppy programming and CL’s inadequate date-handling abilities (mostly the former). This problem could have been avoided. The program that accepted the user’s input could have validated the date. But even that might have not been sufficient. If the user had keyed a valid, but unreasonable, date, there might still have been a problem.
In the following paragraphs I am going to provide you with some routines that can turn CL into a decent date-handler. As for the sloppy programming, that part’s up to you.
Minimal CL Date-Handling
CL has not won any awards for its date-handling ability. Here is a complete list of the CL commands that manage dates.
1. CVTDAT
That’s not much of a list, huh? The Convert Date (CVTDAT) command was designed to convert dates from one format to another. Since it chokes on invalid dates, it can also be used to verify that a variable contains a valid date value.
Here’s a typical example that uses minimal CL to validate dates from a prompt screen. When the user requests a report, the system prompts for a range of dates.
Generate Some Report 1/12/05 12:00:00 Enter a range of dates. You may leave the ending date blank if the report is to be run for only one day. Beginning date ..............................: ______ Ending date .................................: ______ F3=Cancel request Enter=Generate report |
Here’s the DDS for the display file. Besides the prompt format, there is a message subfile for error messages.
Here’s the code for an OPM CL program.
/* =============================================================== */ /* Prompt for a range of dates and submit to batch. */ /* =============================================================== */ /* To compile: */ /* CRTCLPGM PGM(xxx/JKL002C) SRCFILE(xxx/QCLSRC) SRCMBR(JKL002C) */ /* =============================================================== */ pgm dclf JKL002D dcl &WBgnDate *char 8 dcl &WEndDate *char 8 dcl &MsgTxt *char 78 dcl &MsgKey *char 4 dcl &Sender *char 80 MonMsg cpf0000 exec(GoTo Error) Prelims: /* Determine the name of the program so the display */ /* file can reference the program message queue. */ SndPgmMsg msg('/* */') ToPgmQ(*same) + MsgType(*info) KeyVar(&MsgKey) RcvMsg PgmQ(*same) MsgType(*info) MsgKey(&MsgKey) + Rmv(*yes) Sender(&Sender) ChgVar Var(&PgmNam) Value(%SST(&Sender 27 10)) RmvMsg Clear(*all) /* loop until F3 pressed or data is valid */ GetInput: SndF RcdFmt(MSGCTL) SndRcvF RcdFmt(FORMAT01) If (&IN03) Then(Do) SndPgmMsg Msg('Request was cancelled.') MsgType(*comp) Return EndDo /* Validate the input. Error messages go to the subfile. */ RmvMsg Clear(*all) CvtDat Date(&SBgnDate) ToVar(&WBgnDate) + FromFmt(*job) ToFmt(*yymd) ToSep(*none) If (&SEndDate *eq ' ') Then(Do) ChgVar Var(&SEndDate) Value(&SBgnDate) EndDo CvtDat Date(&SEndDate) ToVar(&WEndDate) + FromFmt(*job) ToFmt(*yymd) ToSep(*none) If (&WEndDate *lt &WBgnDate) Then(Do) SndPgmMsg MsgID(JKL1001) MsgF(JKLMSG) ToPgmQ(*same) GoTo GetInput EndDo EndInput: SbmJob Job(SomeJob) + Cmd(Call JKL009C (&SBgnDate &SEndDate)) SndPgmMsg Msg('Your request has been submitted for + processing.') MsgType(*comp) Return Error: RcvMsg MSGQ(*PgmQ) MsgType(*excp) MSG(&MsgTxt) MonMsg MsgID(cpf0000) SndPgmMsg MSG(&MsgTxt) ToPgmQ(*same) MonMsg MsgID(cpf0000) GoTo GetInput EndPgm
For this program to run correctly, you’ll need a message description.
CrtMsgf MsgF(xxx/JKLMSG) AddMsgD MsgID(JKL1001) + MsgF(JKLMSG) + Msg('Ending date must not be before beginning date.')
That doesn’t look so hard, does it? The CVTDAT commands will generate an escape message, such as CPF05555 (Date not in specified format or date not valid) and CPF0557 (Date too short for specified format) if the user keys an invalid date. The global Monitor Message (MONMSG) command kicks in and branches to the Error routine, which sends the message to the program message queue, from which the display file retrieves and displays it to the user. One would think that any CL programmer could at least do this much to verify the accuracy of a date.
This example also includes one check for reasonableness: the first date in the range must not be after the second date in the range.
Once the user has entered valid dates, this program submits another program to batch, passing along the dates as parameters. Since it’s possible that this program could run from somewhere else, such as the job scheduler, the submitted program should not assume that the dates are okay, but should perform validation of its own. If a date is found to be invalid, or possibly unreasonable, the program can send an escape message to cancel itself.
pgm (&FromDate &ThruDate) dcl &FromDate *char 6 dcl &ThruDate *char 6 dcl &TempDate *char 6 dcl &Abending *lgl monmsg cpf0000 exec(goto Abend) cvtdat date(&fromdate) tovar(&tempdate) tosep(*none) cvtdat date(&thrudate) tovar(&tempdate) tosep(*none) call somepgm return /* Routine to handle unexpected errors */ Abend: if &Abending then(return) chgvar &Abending '1' sndpgmmsg msgid(cpf9898) msgf(qcpfmsg) msgtype(*escape) + msgdta('Request for some report ended abnormally. + See the job log') endpgm
Had the programmer, who wrote the CL program about which I spoke in the introduction, included such a check, the job queue would not have filled up with requests.
Robust Date-Handling
In many situations a minimal amount of verification may be sufficient, but in other situations you may need something more robust. For example, December 25, 1969, is a valid date, but it may not make sense in some contexts. If you ask a user to enter an ending date for sales history inquiry, does it make sense to allow him to enter some date in the future?
If you really want robust date validation, you’ll have to do what I’ve done, and come up with something of your own. I wrote an RPG module, CLDATERTNS, of date-handling subprocedures designed with CL in mind. You’re free to use it and enhance it. The following table lists the subprocedures that I have chosen to include so far. Here’s the source code for the CLDATERTNS member. The comments at the beginning will tell you how to create the module.
Subprocedure |
Arguments |
Return |
Description |
AddDays |
Date, |
Date |
Add |
AddMonths |
Date, |
Date |
Add |
AddYears |
Date, |
Date |
Add |
CurrDate |
|
Date |
Job date |
CurrMonthBegin |
|
Date |
First |
CurrMonthEnd |
|
Date |
Last date |
DaysDiff |
Date, |
Number |
Number of |
IsNotValidDate |
Date |
Logical |
True if |
IsValidDate |
Date |
Logical |
True if |
MonthBegin |
Date |
Date |
First |
MonthEnd |
Date |
Date |
Last date |
PrevMonthBegin |
|
Date |
First |
PrevMonthEnd |
|
Date |
Last date |
The dates used by these routines are six-byte character values in job date format. Although I use them with *MDY dates, I have briefly tested with other date formats and the routines appear to work correctly. Numbers are five-digit packed decimal values with no decimal positions. Logical values are one-byte each, with 0 and 1 meaning false and true, respectively.
All routines except IsNotValidDate and IsValidDate send escape message USR2101 if you pass them an invalid date. The AddDays, AddMonths, and AddYears routines send escape message USR2102 if you pass them an invalid packed decimal argument. Here are the commands to create the message file and the messages. Feel free to rename the messages or put them in another message file.
CrtMsgF MsgF(xxx/USRMSG) AddMsgD MsgID(USR2101) MsgF(xxx/USRMSG) Msg('Value ''&1'' is not a valid date.') Fmt((*char 6)) AddMsgD MsgID(USR2102) MsgF(xxx/USRMSG) Msg('Value X''&1'' is not a valid decimal value.') Fmt((*char 6))
Let’s take the same application but make it more robust. I’ve made a slight change to the display file; I’ve added attributes to display invalid date fields in reverse image.
The CL program has changed considerably. The biggest change is that I’ve replaced one OPM CL program with an ILE program built from a CL module and the CLDATERTNS module. As I wrote in “Optional Parameters and CL Procedures,” there are good reasons to dump OPM CL. This application provides yet another reason.
The revised CL uses the same basic logic, but the CVTDAT commands are gone, replaced with calls to procedures from CLDATERTNS. Rather than let the global MONMSG pick up exceptions, I’ve taken control of the validation process. This lets me check both dates for validity in one pass; whereas the minimal version stops looking for errors when a date proves invalid. Since I’m not passing along the system escape messages, I need a message of my own.
ADDMSGD MSGID(JKL1003) MSGF(JKLMSG) + MSG('Date &1 is invalid.') + Fmt((*CHAR 6))
I’ve added one additional test. I’ve added code to verify that the dates are not more than 60 days in the past or 30 days in the future. The error message for this test requires that message JKL1002 be defined in the JKLMSG message file.
ADDMSGD MSGID(JKL1002) MSGF(JKLMSG) + MSG('Dates must be between &1 and &2.') + Fmt((*CHAR 6) (*CHAR 6))
To create the program requires that I first create the two modules, and then bind them together to form a program. The instructions to create module CLDATERTNS are in the source code. Here are the remaining steps.
CrtCLMod Module(xxx/JKL003C) SrcFile(xxx/QCLSRC) SrcMbr(JKL003C) CrtPgm Pgm(xxx/JKL003) Module(JKL003C CLDATERTNS)
For Want of a Valid Date
I’ve concentrated on working with dates, but the lesson we should learn applies to all types of data. Never assume anything is correct. The old rhyme says that a kingdom was lost for want of a nail. In this case, money may have been lost for want of a valid date. Even if no money was lost, the inconvenience caused to users couldn’t have had positive consequences.
Combine these date-handling routines with Cletus’ short-cut date entry technique, and you may be on to something. But that’s a subject for another day.
Click here to contact Ted Holt by e-mail.