As of V3R1, the AS/400 and RPG now support date and time data types. Prior to this support, numeric fields were often used to represent date and time data. Custom written programs then had to be developed to manipulate, validate, and interpret dates.
With date and time fields comes a whole host of added support to validate, format, compare, and manipulate dates and times. You may still have the need to write some custom date/time routines, but development of these will be greatly simplified using the new data types.
Declaring Date/Time Fields
Date and time data type fields may defined either in physical files using DDS, or as internal fields in RPG (using D-specs). Timestamp fields, which include both date and time (including micro-seconds), may also be defined.
When defining date fields in D-specs, the INZ keyword may be used to assign an initial value to the date/time field, as in:
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords++++++++++++++++++++++++++++ D @CurrDate S D inz(d'2000-01-01') D @CurrTime S T inz(t'23:59:59') D @CurrTimestmp S Z inz(*sys)
Note that a value of *SYS may be specified to initialize a date, time, or timestamp to the current system date/time.
Date and time fields may be defined in a number of different formats. The format determines how date and time field is displayed and printed. Use the DATFMT keyword in D-specs or H-specs to define the format of a date field. The available date formats are (note the separator characters):
See IBM's online RPG documentation for a complete description of date formats, as well as minimum and maximum values for each format.
The TIMFMT keyword is used in D-specs or H-specs to define the format of a time field.
There is only one format available for timestamp fields:
The last 6-positions of the timestamp field are microseconds (timestamp fields are often used to generate unique keys for a file).
When declaring date or time fields in an RPG program, if DATFMT or TIMFMT is not specified on the D-spec, the formats will default to the DATFMT and TIMFMT specified on the H-spec. If these keywords are not defined on the H-spec, the format will default to *ISO.
Validating Date/Time values
It's one thing to be able to initialize date/time fields at compile time, but in the real world, more often than not, we need to populate date/time fields with run-time data.
As of V4R5, display files don't support date or time data-type fields. Therefore, if we need to allow a user to enter a date or time, we must first define a numeric or character field, test the field for a valid value, and then move that value to a date or time field.
Date fields are stored internally (in memory) as an integer representing a number of days from a given base date. Sometimes this format is referred to as super-julian. When ever the date is to be displayed, printed, written to a file, etc, an algorithm is run to convert the date from an integer to the specified format.
This is the same way date fields are stored in most PC applications as well. To illustrate this, open a workbook in Microsoft Excel. Enter "12/31/1999" (without quotes) in a cell. It will be interpreted and displayed as a date.
Now, change the format of the cell to General. The value displayed now will be 36525, which is the number of days from January 1, 1900 - the base date used in Excel. Change the number to 2, and re-format the cell as a date (make sure to include year). The date now displayed will be January 2, 1900.
This is actually very similar to the method the AS/400 uses to store dates. One limitation of Excel however is that dates before 1/1/1900 are not supported. Depending on the date format used, the AS/400 will support dates as early as 1/1/0001 and as late as 12/31/9999. Time and timestamp fields are stored in a similar (slightly more complex) manner.
Because of the way dates and times are stored internally, the AS/400 is very intolerant of bad data. If you try to move an invalid date to a date field you will immediatly get an error. This is an important consideration. Many applications use special values of all 9's or all 0's to indicate certain meanings. For example, a program may assign a value of 99999999 to a Date-Processed field to indicate the records has not been processed yet. If you attempt to move these values to a date field, you will get a run-time exception.
Therefore, if there is any question about the validity of a date or time, you should use the TEST opcode. TEST will check for a valid date, time, or timestamp format in a numeric or character field. An opcode extender of D (date), T (time), or Z (timestamp) is used to indicate the type of data being tested for. Use factor-1 to indicate the format you're testing for, and the result field for the data being tested. An error indicator will be turned on if the data is invalid. Some examples:
When the TEST opcode is used to check a character field, the separator character is also tested.
Note that the default date separator may overridden in D-specs or H-specs with the DATSEP keyword.
If the TEST opcode fails, attempting to move the data to a date or time field will also fail. If it does not fail, the data may be safely moved to a date/time field.
Working with Date/Time data
Once you've verified that a character or numeric field has valid date or time data in it, you may move it to a date or time data type using the MOVE operation code.
Note that in this example, @Num is in the same format as @Date (*usa). The move operation will assume that factor-2 and the result field are the same format. If this is not the case, factor-1 must be used to indicate the format of the source data. For example:
By coding *usa in factor-1 of the MOVE statement, we are telling the compiler to convert the data in @Num from the *usa format to whatever format @Date is defined as. After this operation, @Date will contain the value '1999-12-31'. It is generally considered good practice to always code factor-1 on date move operations. By using the same format on the MOVE as you did on the TEST, your are guaranteed to always have a successful move.
Once data is moved to a date/time field, it can be manipulated or compared to other date/time fields easily. Dates can be compared to each other without concern for the format. For example a date in format ccyy/mm/dd can accurately be compared to a date in format mm/dd/ccyy.
Elements from date or time fields (month, seconds, etc.) may be extracted from to a numeric field using the EXTRCT operation code. Factor-2 should contain the date or time field, followed by ":" and a duration code. Valid duration codes are *YEARS, *MONTHS, *DAYS, *HOURS, *MINUTES, *SECONDS, *MSECONDS. Abbreviated duration codes may also be used: *Y, *M, *D, *H, *MN, *S, *MS.
Dates may also be manipulated by using the ADDDUR and SUBDUR operation codes (add/subtract duration).
SUBDUR has the same format and functionality as ADDDUR with one additional benefit. If date fields are used in both factor-1 and factor-2, it will calculate the duration between the two dates and update the result field. The result field may be calculated as any one of the valid duration codes.
One thing to keep in mind is that using ADDDUR and SUBDUR on dates with durations other that *DAYS may cause unexpected results. For example, look at the following code:
Even though we added one month and then immediately subtracted one month from a date, the result is not the same as what we started with. While not necessarily wrong, peculiarities like this should be understood before using this functionality.
As mentioned earlier, RPG's built in date and time support probably won't replace all your custom date routines, but it will make developing these date routines much easier. Following are some examples of how you can simplify previously mundane programming tasks.
Checking for leap year
This used to involve a series of division statements (first by 4, then by 100, then by 1000) with work fields to capture the remainders. Now it is as easy as checking the year for a valid date of February 29.
The following piece of code may be used to check if the 4-digit field @Year is a leap-year:
If *in99 is set on, @TestDate is invalid, and @Year is not a leap year.
Last day of month
Determining the last calendar day of a month used to involve arrays containing the number of days in each month and a routine to check for leap year. Calculating the last day of the month for a year/month combination (ccyymm) can now be accomplished in four lines of code:
The first line of code sets the numeric field @WorkDate to the first day of the month. The next line of code moves this to a date data-type field (@Date).
The third line of code adds one month to @Date to arrive at the first day of the next month. The last line subtracts one day from this date to arrive at the last day of @YrMth.
Day of week
There are a number of ways to figure the day of week for a given date. Prior to RPG date/time support none of these methods were pretty. The code required now is fairly straightforward to calculate the day-of-week for given date (@Date):
You could then easily add an array to retrieve the day of week names.
Note that we used the built-in-function %REM to determine the remainder. We'll cover built-in-functions in detail in the next section. The DIV and MVR operation codes could have been used instead, but it is somewhat cleaner with the %REM function.
Even though RPG IV's built in date and time support make programming tasks like the above much simpler, in later sections we'll explain methods to make tasks like this even easier by using ILE procedures.
ILE procedures allow you to bundle your code and create your own versions of built in functions. Instead of executing a subroutine or calling a separate program, ILE procedures can be called directly in expressions. The examples above could easily be re-coded so they may be execute like the following:
For now, this is just to whet your appetite. We'll cover the specifics of how to do this in the ILE sections.
Send mail to firstname.lastname@example.org
with questions or comments about this web site.
Last Modified: August 16, 2000