Newsletters   Subscriptions  Forums  Store   Career  Media Kit  About Us  Contact  Search   Home 
fhg
Volume 5, Number 3 -- January 19, 2005

Date-Handling in CL Procedures

by 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 value

Description

AddDays

Date, number

Date

Add number of days to a date

AddMonths

Date, number

Date

Add number of months to a date

AddYears

Date, number

Date

Add number of years to a date

CurrDate

 

Date

Job date

CurrMonthBegin

 

Date

First date of the current month

CurrMonthEnd

 

Date

Last date of the current month

DaysDiff

Date, date

Number

Number of days between first and second dates. Positive number means second date is after first date. Negative number means second date is before first date.

IsNotValidDate

Date

Logical

True if date is not valid, false if date is valid

IsValidDate

Date

Logical

True if date is valid, false if date is invalid

MonthBegin

Date

Date

First date of a month

MonthEnd

Date

Date

Last date of a month

PrevMonthBegin

 

Date

First date of the previous month

PrevMonthEnd

 

Date

Last date of the previous month


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.

Sponsored By
ADVANCED SYSTEMS CONCEPTS

SEQUEL
beats the pants off
Query/400 !

Read the
Top Ten Reasons
why you should switch!

(Click Here)

FREE Trial - Call 847-605-1311
or use our online
Trial Request Form


Technical Editors: Howard Arner, Joe Hertvik, Ted Holt,
Shannon O'Donnell, Kevin Vandever
Managing Editor: Shannon Pastore
Contributing Technical Editors: Joel Cochran, Wayne O. Evans, Raymond Everhart,
Bruce Guetzkow, Marc Logemann, David Morris
Publisher and Advertising Director: Jenny Thomas
Advertising Sales Representative: Kim Reed
Contact the Editors: To contact anyone on the IT Jungle Team
Go to our contacts page and send us a message.


THIS ISSUE
SPONSORED BY:

Advanced Systems Concepts
WorksRight Software
Guild Companies


BACK ISSUES

TABLE OF
CONTENTS
Date-Handling in CL Procedures

Extracting Zoned and Packed Decimal Values from Character Fields

Admin Alert: More on Preparing for OS/400 V5R1 to V5R3 Upgrades


The Four Hundred
IBM to Promote the iSeries During the NFL Playoffs

Migration to Java Is Paying Off, Intentia Says

Will IT Vendors Set Up a Patent Trust?

Four Hundred Stuff
Testing At iSeries Shops Not Up to Snuff, Original Finds

New PowerTech Product Cracks Down on Special Authorities

iSeries Is Center of Lean IT Operation At adidas-Salomon Canada

Four Hundred Monitor


Copyright © 1996-2008 Guild Companies, Inc. All Rights Reserved.
Guild Companies, Inc. (formerly Midrange Server), 50 Park Terrace East, Suite 8F, New York, NY 10034
Privacy Statement