
As soon as we handled Y2K I thought when we reached date 2039 we would have something in place to prevent the "2039 problem".
What is the "2039 problem"? This only occurs with dates with years that are just two characters. A date data type, character or numeric representation of a date, that has a two character year uses the following rule to determine the century:
- 40 – 99: Century is assumed to be "19"
- 00 – 39: The assumption is that the century is "20"
We are 14 years away from 2039, but soon there a going to be applications that will need to use dates beyond that year. When I attended COMMON's Navigate in Toronto, in November 2024, Barbara Morris, IBM's lead developer for the RPG compiler, told us that it would be up to us, the IBM i developers, to remedy the issue. The way RPG handles dates will remain unchanged.
She informed us that within the latest Technology Refresh, IBM i 7.5 TR5 and IBM i 7.4 TR11, there would be some new features that would allow us to identify what could be a "2039 problem" issue.
The first is a new compile parameter, DATEYY. When the program or module is compiled the compiler looks for any scenario where a variable appears to be used for a six long date, and then will identify it in the compile listing.
DATEYY can contain three values
- DATEYY(*ALLOW): Allow all date formats, in other words do not perform any validation
- DATEYY(*WARN): If it finds something it thinks is a two character year it will issue a warning compile error, level 10
- DATEYY(*NOALLOW): If finds something that could be a two character year the compile will error with a level 30 error
The default value is "*ALLOW".
If I was to prompt the Create Bound RPG command, CRTBNDRPG, I would need to page down to the last screen of parameters and the last one is the DATEYY:
Create Bound RPG Program (CRTBNDRPG) Type choices, press Enter. Target CCSID . . . . . . . . . . > *SRC 1-65534, *SRC, *JOB REQUIRE PROTOTYPE FOR EXPORT . . > *NO *WARN, *REQUIRE, *NO MINIMUM OUTPUT LINE LENGTH . . . > 100 100-32754 DATE WITH 2-DIGIT YEARS . . . . > *ALLOW *ALLOW, *WARN, *NOALLOW |
The same is true with the Create RPG Module command, CRTRPGMOD, the DATEYY is the last option on the last screen:
Create RPG Module (CRTRPGMOD) Type choices, press Enter. Target CCSID . . . . . . . . . . > *SRC 1-65534, *SRC, *JOB REQUIRE PROTOTYPE FOR EXPORT . . > *NO *WARN, *REQUIRE, *NO MINIMUM OUTPUT LINE LENGTH . . . > 100 100-32754 DATE WITH 2-DIGIT YEARS . . . . > *ALLOW *ALLOW, *WARN, *NOALLOW |
Personally I do not like changing the defaults of IBM commands. If there is ever a PTF or a new release the command could be changed back to the default values, which would start causing unexpected results. Fortunately DATEYY is also a control option that I can use within the source code of the program. The control option uses the same values and the command parameter.
Below is my first example with the DATEYY control option:
01 **free 02 ctl-opt dateyy(*allow) ; 03 dcl-s ThisDate date inz(*sys) ; 04 dsply ('Short date = ' + %char(ThisDate : *jobrun)) ; 05 dsply ('Long date = ' + %char(ThisDate : *longjobrun)) ; 06 *inlr = *on ; |
Line 2: The DATEYY control option in this example contains "*ALLOW", therefore no validation of the dates is performed.
Line 3: I have defined this date field, ThisDate, and initialized it with the current system date.
Line 4: I want to display the value in ThisDate, so I use the Display operation code, DSPLY. I cannot concatenate a date field without converting it to character, which I do with the %CHAR Built in Function, BiF. *JOBRUN in the second parameter will format the date into the date format of this program, as I am in the USA it will be MMDDYY format.
Line 5: Is the same as line 4, except it uses the new value of *LONGJOBRUN, which for me is MMDDYYYY.
After compiling the program I call it, and the following is displayed:
DSPLY Short date = 01/15/25 DSPLY Long date = 01/15/2025 |
I then modified the DATEYY to be:
02 ctl-opt dateyy(*warn) ; |
When I compiled the program I received a warning message in the compile listing:
A d d i t i o n a l D i a g n o s t i c M e s s a g e s Msg id Sv Number Seq Message text *RNF0201 10 6 000600 WARNING: A DATE WITH 2 DIGITS FOR THE YEAR ONLY SUPPORTS THE YEARS 1940 TO 2039. REASON CODE: JOBRUN. |
The sequence number is 6 which is the equivalent of line 4 for the code I showed, as there are two blank source lines before line 4.
As the message is a warning the program object is still created.
Then I changed to control option to *NOALLOW:
02 ctl-opt dateyy(*noallow) ; |
When I compiled the program I received a level 30 error, and the object was not created.
A d d i t i o n a l D i a g n o s t i c M e s s a g e s Msg id Sv Number Seq Message text *RNF0203 30 6 000600 A DATE WITH 2 DIGITS FOR THE YEAR IS NOT ALLOWED DUE TO DATEYY(*NOALLOW). REASON CODE: JOBRUN. |
With older RPGLE code, which uses columns, there are two other things that will cause issues:
- UDATE: As it has a two character year
- TIME: This operation code, which is only available when using columns, will return the date and time. If it defined as 12 long it will return a string: HHMMSSDDMMYY, notice that there is a two character year (YY), your date may be difference depending on the date format of your system.
Here is my fixed format program:
01 H dateyy(*allow) 02 D Message S 50 03 D wkTime S 12 0 04 D wkDate S D 05 C *mdy move UDATE wkDate 06 C eval Message = 'UDATE = ' + %char(wkDate) 07 C Message dsply 08 C time wkTime 09 C *mdy move wkTime wkDate 10 C eval Message = 'WKTIME = ' + %char(wkTime) 11 C Message dsply 12 C eval Message = 'WKDATE = ' + %char(wkDate) 13 C Message dsply 14 C eval *inlr = *on |
Line 1: This is the DATEYY in a fixed format H-spec.
Line 5: Moving the value within UDATE to the date field, in MMDDYY format.
Line 6: In fixed format the message for the DSPLY operation code has to be in Factor 1. Therefore, I am creating a string in the variable Message.
Line 7: The contents in Message is displayed.
Line 8: I move the time and six long date into the variable wkTime, which is 12 long.
Line 9: I move the wkTime into the date field wkDate in MMDDYY format.
Lines 10 – 13: I display the values of wkTime and wkDate.
As the control option is DATEYY(*ALLOW) the program successfully compiles. When I called the program the following it is displayed:
DSPLY UDATE = 2025-01-21 DSPLY WKTIME = 194556012125 DSPLY WKDATE = 2025-01-21 |
I changed the control option to:
01 H dateyy(*warn) |
I get two warnings:
8 C *mdy move UDATE wkDate ======> aaaa bbbbb *RNF0201 10 a 000800 WARNING: A DATE WITH 2 DIGITS FOR THE YEAR ONLY SUPPORTS THE YEARS 1940 TO 2039. REASON CODE: DATFMT. *RNF0201 10 b 000800 WARNING: A DATE WITH 2 DIGITS FOR THE YEAR ONLY SUPPORTS THE YEARS 1940 TO 2039. REASON CODE: UDATE. 13 C *mdy move wkTime wkDate ======> aaaa *RNF0201 10 a 001300 WARNING: A DATE WITH 2 DIGITS FOR THE YEAR ONLY SUPPORTS THE YEARS 1940 TO 2039. REASON CODE: DATFMT. |
The fist line has two warning errors:
- The *MDY date format will cause the result to have a two character year
- UDATE only has a two character year
The second line just has the warning about the *MDY date format.
Next is the *NOALLOW:
01 H dateyy(*noallow) |
Instead of warning errors for the two lines I received three compile errors:
8 C *mdy move UDATE wkDate ======> aaaa bbbbb *RNF0203 30 a 000800 A DATE WITH 2 DIGITS FOR THE YEAR IS NOT ALLOWED DUE TO DATEYY(*NOALLOW). REASON CODE: DATFMT. *RNF0203 30 b 000800 A DATE WITH 2 DIGITS FOR THE YEAR IS NOT ALLOWED DUE TO DATEYY(*NOALLOW). REASON CODE: UDATE. 13 C *mdy move wkTime wkDate ======> aaaa *RNF0203 30 a 001300 A DATE WITH 2 DIGITS FOR THE YEAR IS NOT ALLOWED DUE TO DATEYY(*NOALLOW). REASON CODE: DATFMT. |
Another special value that is a two character year is UYEAR, that will cause the warning and error messages too.
I switched back to modern RPG for my next scenario. What happens if I define a date variable with formats I know have only two character dates? For example *MDY and *JUL.
01 **free 02 ctl-opt dateyy(*warn) ; 03 dcl-s MdyDate date(*mdy) inz(*sys) ; 04 dcl-s JulianDate date(*jul) inz(*sys) ; |
Line 3: MdyDate is defined with the MMDDYY format.
Line 4: JulianDate is defined with Julian date format, YYDDD.
When this is compiled warning messages are returned by the compiler:
4 dcl-s MdyDate date(*mdy) inz(*sys) ; ======> aaaa *RNF0201 10 a 000400 WARNING: A DATE WITH 2 DIGITS FOR THE YEAR ONLY SUPPORTS THE YEARS 1940 TO 2039. REASON CODE: DATFMT. 5 dcl-s JulianDate date(*jul) inz(*sys) ; ======> aaaa *RNF0201 10 a 000500 WARNING: A DATE WITH 2 DIGITS FOR THE YEAR ONLY SUPPORTS THE YEARS 1940 TO 2039. REASON CODE: DATFMT. |
I am not going to bother to show what happens with DATEYY(*NOALLOW) as you know it will generate compile errors.
I admit that I am disappointed that we have resolve the "2039 problem" without IBM changing the way the date variables defined as *MDY, or other dates with a two character year. What I have written about above is going to make it a whole lot easier to fix the problem.
The control options for my programs, at work, all reside in a separate source member that is included into the source of every program with a /COPY. I can add the DATEYY(*WARN) to this source member and then every time I compile a program it will check the dates, and generate warning errors where necessary that will alert me as to what needs to be changed.
You can learn more about the DATEYY control option from the IBM website here.
This article was written for IBM i 7.5 TR5 and 7.4 TR11.
Does Cobol have a way to do this?
ReplyDeleteAs there was not any Cobol enhancements announced with the latest TRs I would say that there was not a Cobol equivalent released.
Delete"We are 24 years away from 2039" - are you sure or did you ask Chat GPT about it? ;-)
ReplyDeleteOops. I have fixed it. Thank you for bringing that to my attention.
DeleteThere is a reason why IBM cannot fix the 1939 problem:
ReplyDeleteLet's say there is an old date 1941. It could be the date birth of a person or the date a building was built. and that date was written with a 2-digit year. If
IBM will change it and from now it is 2041' it will be wrong.
The only solution is not to allow short dates and use the warning.
The problem may be already now because of future dates like mortgage payment calculations.
Therefore, I would strongly suggest using dateyy(*noallow) in all programs. You cannot write a program that in a few years from now will crash.
There is a wheeze that may help.
ReplyDeletePhysical file DDS:-
A R DATEFILER
A KEY 10A
A DATE L
A K KEY
Logical file DDS
A R DATEFILER PFILE(DATEFILE)
A KEY
A DATE DATFMT(*YMD)
A K KEY
Note - all the physical fields are listed in the logical DDS
If you use the logical to read the data, the date comes in in the format in the DATFMT. So you may not need to change your program at all.
Careful though. If something puts a data in the physical that is not between 1940-01-01 and 2039-12-32, when you read that record using the logical, you will get an error.
"Line 2: The DATEYY control option in this example contains "*ALL", therefore no validation of the dates is performed." - Don't you mean "*ALLOW"?
ReplyDeleteGood catch. Correction has been made.
Delete