
After my post about IBM providing a control option to look for dates that could fall foul of the 2039 issue, someone messaged me to ask:
I think the problem is when date is converted an external representation and converted back. I think of a display file, if the user input in a display file say an expiry date in 2041 how would you handle the display and conversion?
That is a good question. There are times when users should have to enter eight long dates for things like dates of birth, but other dates can be used on a display file as six size long and stored in the database as a date data type. With the impending "2039 issue" how would I manage the need to change from the "1940" rule to another?
To demonstrate how I think I will manage the "2039 issue" for this scenario I need a display file:
01 A DSPSIZ(24 80 *DS3) 02 A R SCREEN 03 A 2 2'Date date . .' 04 A DATE_DATE L B 2 17DATFMT(*USA) 05 A 4 2'Date char 8 .' 06 A DATE_8 8 0B 4 17EDTWRD('0 / / ') 07 EDTMSK(' & & ') 08 A 6 2'Date char 6A .' 09 A DATE_6A 6 0B 6 17EDTCDE(Y) 10 EDTMSK(' & & ') 11 A 7 2'Date char 6B .' 12 A DATE_6B 6 0B 7 17EDTWRD('0 / / ') 13 EDTMSK(' & & ') |
This is deliberately a remarkably simple display file just to be used to display and accept input for the four "date" fields.
Line 4: I am using a date type field, data type "L", and I want to display and handle input in USA format.
Lines 6 and 7: Here is a number masquerading as a date field. It is eight long. It uses edit word to format the way the number will be presented on a screen, line 6, and uses an Edit Mask, line 7, that will have the user enter the date hopping from month to day to year.
Lines 9 and 10: This is a six long "date". I am using the "Y" edit code to format it, and an edit mask to for entry.
Lines 12 and 13: A second six long "date". Here I am using an edit word, rather than edit code. I will explain why when the compiled display file is shown.
Next I need a program to populate the display file's fields and manage the input from it. The program's code is:
01 **free 02 ctl-opt dateyy(*warn) bnddir('TESTBNDDIR') dftactgrp(*no) ; 03 dcl-pr ConvertDate packed(8:0) ; 04 *n packed(6:0) value ; 05 end-pr ; 06 dcl-f TESTDSPF workstn ; 07 dcl-s wkDate date ; 08 DATE_DATE = %date() ; 09 DATE_8 = %dec(%date():*longjobrun) ; 10 DATE_6A = %dec(%date():*jobrun) ; 11 DATE_6B = DATE_6A ; 12 exfmt SCREEN ; 13 wkDate = ConvertDate(DATE_6B) ; 14 dsply ('wkDate = ' + %char(wkDate : *iso)) ; 15 *inlr = *on ; |
Line 2: The control options contain the new DATEYY control option, I have it with a "*WARN", therefore, if the compiler thinks something is a six long date it will issue a compile warning error for each occurrence. I also have the option to name a binding directory, this directory contains the name of a module I want to bind to this program at create time
Lines 3 – 5: This is the procedure prototype definition. I will pass to it a six long packed numeric value, and have a date returned.
Line 6: Here is the definition for the display file I showed above.
Line 7: I need date field defined to contain the result of the procedure, which I defined on lines 3 – 5.
Line 8: I am initializing the first field in the display file with the current date.
Line 9: Here I am moving the current date to the eight long numeric field in the display file. If you are unsure of what the "*LONGRUN" means click here.
Line 10: Moving the six current date to the first of the six long date fields. This is the one with the EDIT(Y).
Line 11: Move the contents of the first six long display file field to the second.
Line 12: Execute format the display file's record format.
Line 13: When control retuned to the program I call the procedure, passing to it the second long date field, and receive into the variable wkDate the result from the procedure.
Line 14: Display the returned result.
I am not going to display the procedure yet. All we need to know now is the procedure ConvertDate is contained within the module TESTRPG0
Rather than use the Work with Directory Entries command, WRKBNDDIRE, I can check what is in the binding directory, TESTBNDDIR, used by this program using the following SQL:
01 SELECT ENTRY_LIBRARY,ENTRY,ENTRY_TYPE 02 FROM QSYS2.BINDING_DIRECTORY_INFO 03 WHERE BINDING_DIRECTORY_LIBRARY = 'MYLIB' 04 AND BINDING_DIRECTORY = 'TESTBNDDIR' |
Line 1: I only want to return the library the object is found in, object name, and the type of object it is.
The results are:
ENTRY_ ENTRY LIBRARY ENTRY _TYPE ------- -------- ------- MYLIB TESTRPG0 *MODULE |
After compiling the program, I call it and the display is shown with the current date in the various fields:
Date date . . 01/25/2025 Date char 8 . 01/25/2025 Date char 6A . 1/25/25 Date char 6B . 01/25/25 |
Notice that the date displayed for the first six long field, which uses the EDTCE(Y), does not have a leading zero for the month. Personally, I don't like the way this looks. I used an edit word for the second six long date it has the leading zero.
When enter is pressed the ConvertDate procedure is called. Its source code is:
01 **free 02 ctl-opt nomain ; 03 dcl-proc ConvertDate export reqproto(*no) ; 04 dcl-pi *n date ; 05 InDec6 packed(6:0) ; 06 end-pi ; 07 dcl-ds Date6 qualified ; 08 Month char(2) ; 09 Day char(2) ; 10 Year char(2) ; 11 end-ds ; 12 dcl-ds Date8 qualified ; 13 Century char(2) ; 14 Year char(2) ; 15 Month char(2) ; 16 Day char(2) ; 17 end-ds ; 18 dcl-s wkDate date ; 19 evalr Date6 = '0' + %char(InDec6) ; 20 eval-corr Date8 = Date6 ; 21 if (Date8.Year < '70') ; 22 Date8.Century = '20' ; 23 else ; 24 Date8.Century = '21' ; 25 endif ; 26 test(de) *iso0 Date8 ; 27 if (%error) ; 28 clear wkDate ; 29 else ; 30 wkDate = %date(Date8 : *iso0) ; 31 endif ; 32 return wkDate ; 33 end-proc ; |
Line 2: As this is a module that only contains procedures that are called by other programs and procedures I need the NOMAIN control option.
Line 3: Start of the procedure. Notice that there is no procedure definition. When I use the REQPROTO(*NO) keyword I tell that compiler that this source does not need a procedure definition. I presume it can determine one at compile time what it should be by looking at the procedure interface definition.
Lines 4 – 6: This is the procedure interface. I have a six long packed numeric passed to this procedure, and a date is returned.
Lines 7 – 11: The fastest way to switch a numeric presentation of a date from one format to another is by using data structures. This is the data structure for a six long "date".
Lines 12 – 17: This is the definition for the data structure definition for the eight long "date". Notice that the order of the subfields are different. While the six long is DDMMYY this is CCYYMMDD. Which means it also contains a subfield for the century.
Line 18: This is the variable that will contain the date that is returned to the calling program.
Line 19: I move the value in the number that was passed to the program to the data structure Date6. I am using the EVALR operation code as it will right adjust what is passed to it. I do this as the %CHAR built in function removes the leading zero from any number that starts with a number. This will take the leading zero off the month number, which would cause problems later in the procedure. By adding a leading zero to the result of %CHAR is the month is only a single digit, and not be moved in the data structure is the month is two digits.
Line 20: EVAL-CORR will move all the values from subfields in Date6 into subfields of the same name in Date8. In other words the day, month, and year subfields. Century remains blank.
Lines 21 – 25: This is where I determine the century. I am using a "1970" rule, but could easily use a "1980" rule if I desire.
Line 26: If I am returning a date it has to be valid. This is where I validate it.
Lines 27 – 31: If the date is invalid I clear the date field, making the date "0001/01/01". If the date is valid I transform the number using the %DATE BiF to a date which is moved to the date field.
Line 32: I return the date to the calling program.
When I display the date, line 14 of the calling program, the following is displayed:
DSPLY wkDate = 2025-01-25 |
A perfectly good valid date.
What happens when I enter an invalid date into one of the date fields in the display file, for example:
Date char 6B . 99/25/25 |
As that is an invalid date, the DSPLY shows:
DSPLY wkDate = 0001-01-01 |
Why did I make the procedure that does the date conversion external to the program? If this was production this procedure would exist within a service program. That way I can use the same procedure from any program I want. It also means that, in the future, if I want to change the rule I can just change it here and recreate the service program, no need to change the programs that call the procedure.
As I said this is an extremely simple example. In a production environment I would return an error code that would just be for an invalid date, rather than the date I did here.
I hope that this explanation answers the question I was asked.
This article was written for IBM i 7.5 TR5 and 7.4 TR11.
No comments:
Post a Comment
To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.
Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.