Date Formatting

RPG
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times
Throughout my years in this industry, one thing that has continuously eluded me was an easy way to format a date field into words. For example, how do you translate 2002-01-12 (in ISO format) into Saturday, January 12, 2002?

I have written and reviewed at least a dozen routines to accomplish this type of transformation. Many of them used compile-time tables and translation tables to convert 02 to February and tricky algorithms that would some how figure out that 2002-01-12 is a Saturday.

Since RPG IV supports procedures and it is not 1976 anymore, there should be easier methods to translate a date or time into words.

I've created a procedure, GetDateString(), that greatly simplifies converting date and time values into words. GetDateString() accepts a date value in any valid date format and translates it into the day of the week and month as words, followed by the day and year as numerals. Optionally, you can specify a format for the returned value. I've used this routine extensively on reports and form-based output, and I've found that it comes in extremely handy in CGI RPG IV programming. Here is the syntax for the GetDateString procedure:

 

charVar  = GetDateString( date-value [ : string-format ] ) 


The return value is a character-formatted version of the date specified on the first parameter. The format is based on the format codes specified on the second parameter.

The first parameter is required and must be a valid date value. The date value is normally specified as a date field. Since the date-value parameter is a CONST parameter, a date value in any format can be specified.

The second parameter is optional, and if specified, must contain valid formatting codes for the return value. The formatting codes are the key to creating the return string.

The formatting pattern is the same formatting pattern supported by the strftime() function, which is a C language runtime function. Why is it the same as strftime()? Because strftime() is called by GetDateString() to do the difficult work of converting a date value to words.

If unspecified, the string-format parameter of GetDateString() defaults to '%A, %B, %d, %Y'. This default value is a format that allows a date to be returned in Day, Month, Day, Year format. For example, 2002-01-12 is returned as "Saturday, January 12, 2002." Other formatting codes are supported; the complete set of formatting codes and their meanings is listed in Figure 1.

Note that format codes are case-sensitive. For example, a lowercase %a returns the day as "Tues" whereas the uppercase %A returns "Tuesday." Formatting codes are similar in functionality to the substitution values in OS/400 messages or what is used in user-defined options in PDM. See Figure 1 for details.

Format Code
Description
%a
Abbreviated weekday name (e.g., Mon, Tues, Wed)
%A
Full weekday name (e.g., Sunday, Monday, Tuesday)
%b
Abbreviated month name (e.g., Jan, Feb, Mar, Apr)
%B
Full month name
%c
Date and time representation appropriate for locale
%d
Day of month as decimal number (01 - 31)
%H
Hour in 24-hour format (00 - 23)
%I
Hour in 12-hour format (01 - 12)
%j
Day of year as decimal number (001 - 366)
%m
Month as decimal number (01 - 12)
%M
Minute as decimal number (00 - 59)
%p
Current locale's A.M./P.M. indicator for 12-hour clock
%S
Second as decimal number (00 - 59)
%U
Week of year as decimal number, with Sunday as first day of week (00 - 53)
%w
Weekday as decimal number (0 - 6; Sunday is 0)
%W
Week of year as decimal number, with Monday as first day of week (00 - 53)
%x
Date representation for current locale
%X
Time representation for current locale
%y
Year without century, as decimal number (00 - 99)
%Y
Year with century, as decimal number
%z, %Z
Time-zone name or abbreviation; no characters if time zone is unknown
%%
Percent sign

Figure 1: Date/Time Formatting Codes


Using C runtime functions is getting very popular. Essentially, any C language runtime can be used in an RPG IV program by doing two things.

  1. Include the QC2LE binding directory on the BNDDIR parameter of the CRTBNDRPG or CRTPGM commands, or in the Header specification of the source code as BNDDIR('QC2LE').
  2. Prototype the function using RPG IV Definition specification.


Including the BNDDIR('QC2LE') statement on the Header specification is pretty standard, and it simplifies the compile process by allowing you to forget to specify it at compile time. Believe me: Long, frustrating hours of recompiling can be avoided by simply including that statement on your Header specifications.

Prototyping the C runtime functions is getting to be a widespread practice in RPG IV. I use a standard /COPY member when prototyping C runtime functions. However, for the strftime() function, I needed some additional declarations, so I created the following source member, which must be /COPY'd into source that calls the strftime() function. I'm not going to get into the details of each line of code in this /COPY member; we'll cover that in a future issue. For now, the /COPY we need for the GetDateString() procedure is as shown in Figure 2:

      ** Source member: RPGTIME
0110 D int2            S              5I 0
0120 D int4            S             10I 0
0130 D ptr             S               *

0150 D shortint        S                   Like(int2)
0160 D int             S                   Like(int4)
0170 D longint         S                   Like(int4)
0180 D size_t          S                   Like(int4)

     ** The C language TM structure contains information
     ** about a time value. Since a point to this structure
     ** is typically what is being manipulated, the
     ** Data structure TM is based on the pTM pointer.
0190 D pTM             S                   Like(ptr)
0200 D tm              DS                  based(pTM)
0210 D  tm_sec                             Like(int4)
0220 D  tm_min                             Like(int4)
0230 D  tm_hour                            Like(int4)
0240 D  tm_mday                            Like(int4)
0250 D  tm_mon                             Like(int4)
0260 D  tm_year                            Like(int4)
0270 D  tm_wday                            Like(int4)
0280 D  tm_yday                            Like(int4)
0290 D  tm_isdst                           Like(int4)


     **  STRFTIME converts a TM structure to text, based-on
     **  the formatting characters specified in the 3rd parameter
0320 D strFTime        PR                  Extproc('strftime')  like(int)
0330 D  szTarget                       *   value
0340 D  nTargetLen                         value  Like(size_t)
0350 D  szFormat                       *   value
0360 D  struct_tm                      *   value

Figure 2: /COPY RPGTIME Source Member


The procedure GetDateString() is illustrated in Figure 3. The first parameter is a date value. As mentioned earlier, any date value may be specified. The CONST keyword gives us this capability.

The second parameter may be a quoted character string, a literal value, or a field that contains a valid date-formatting string. Refer back to Figure 1 for a list of the valid formatting codes. I've limited this string to an arbitrary length of 64 characters. It is unlikely that you'll need a longer value, but if you do, simply increase the length of the second parameter and the MYFORMAT local variable.

     ** Source member: RPGTIME
      /IF       NOT DEFINED(RPG_TIME)
      /DEFINE   RPG_TIME
     D GetDateString   PR           256A   VARYING
     D  dts                            Z   Const 
     D  dtFmt                       256A   Const VARYING
 
     D GetDay          PR            10I 0
     D  InputDate                      D   CONST DATFMT(*ISO)
      /ENDIF

Extra Include File

 
     ** Source member: TIMEDS
      /IF       NOT DEFINED(TIME_DS)
      /DEFINE   TIME_DS
     D int4            S             10I 0

      ** The C language TM structure contains information

      ** about a time value. Since a point to this struct this structure

      ** is typically what is being manipulated, the

      ** Data structure TM is based on the pTM pointer.

     D tm              DS
     D  tm_sec                             Like(int4)
     D  tm_min                             Like(int4)
     D  tm_hour                            Like(int4)
     D  tm_mday                            Like(int4)
     D  tm_mon                             Like(int4)
     D  tm_year                            Like(int4)
     D  tm_wday                            Like(int4)
     D  tm_yday                            Like(int4)
     D  tm_isdst                           Like(int4)
 
     D size_t          S                   Like(int4)
 
     D time_t          DS
     D  tm_seconds                         Like(int4)
 
     D time            PR            10I 0 Extproc('time')
     D  time_t                             Like(time_t)
 
     D strFTime        PR            10I 0 Extproc('strftime')
     D  szTarget                    255A   options(*VARSIZE)
     D  nTargetLen                         like(size_t)     value
     D  szFormat                       *   options(*string) value
     D  struct_tm                          like(tm)
      /ENDIF
 

Source for Procedures

     H NOMAIN BNDDIR('QC2LE')
      
     D/COPY QRPGLESRC,RPGTIME
     D/COPY QRPGLESRC,TIMEDS
     ***************************************************************************
     ** G E T  D A T E as  S T R I N G -  Procedure
     ***************************************************************************
     P GetDateString   B                   Export
     D GetDateString   PI            64A   VARYING
     D  dts                            Z   Const 
     D  dtFmt                       256A   Const VARYING
     D
     D inDate          S               D   DATFMT(*ISO)
     D nLen            S             10I 0
     D baseYear        S               D   DATFMT(*ISO)
     D dftFmt          C                   Const('%A, %B %d, %Y')
     D myFormat        S            256A
     D myTime          S                   Like(time_T)
     D myString        S             50A
     D myTM            S                   Like(TM)
     C                   if        %Parms >= 2
     C                   Eval      myFormat = dtFmt
     C                   else
     C                   Eval      myFormat = dftFmt
     C                   endif
     C                   
     C                   Move      *ALLX'00'     tm
     C                   Extrct    dts:*seconds  tm_sec
     C                   Extrct    dts:*minutes  tm_min
     C                   Extrct    dts:*hours    tm_hour
     C                   Extrct    dts:*Days     tm_mday
     C                   Extrct    dts:*Months   tm_mon
     C                   Sub       1             tm_mon
     C                   Eval      baseYear = D'1900-01-01'
     C     dts           SubDur    baseYear      tm_year:*years
      ** Retrieve the date from the input TimeStamp   
      ** We have to use MOVE since EVAL doesn't do this. :(
     C                   MOVE      dts           inDate
      ** GetDay returns the day of the week as a ones-based value
      ** we need 0-based, so subtract 1.
     C                   Eval      tm_wday = GetDay(inDate)-1
     C                   Clear                   tm_isdst
     C                   Eval      myTm = tm
 
     C                   CallP      strFTime(myString : %size( myString ) :
     C                                        myFormat : myTm)
 
     **   Strip off the null terminated string characters from the
     **   returned value.
     C     X'0040'       checkr    myString      nLen
 
     C                   return    %subst(myString:1:nLen)
     PGetDateString    E
 
     P GetDay          B                   EXPORT
      ***************************************************************************
      ** G E T D A Y  (Get day of week)
      **   Returns 1 through 7 for the day of the week.
      ***************************************************************************
     D GetDay          PI            10I 0
     D  InputDate                      D   CONST DATFMT(*ISO)
     ** Base date is based on calendar changed date
     D BaseDate        S               D   Static INZ(D'1582-10-14') 
     D nDayOfWeek      S             10I
     D nDays           S             10I 0 
     C                   TEST(E)                 InputDate  
     C                   If        %ERROR
     C                   Return    -1
     C                   Endif
     C     InputDate     SubDur    BaseDate      nDays:*DAYS 
 
     ** Passing the number of days since Oct 14, 1582 to CEEDYWK
     ** gives us back the day of the week.
     C                   CALLB     'CEEDYWK'
     C                   Parm                    nDays
     C                   Parm                    nDayofWeek
 
     C                   return    nDayOfWeek 
     PGetDay           E 

Figure 3: GetDateString Procedure


As mentioned, the default formatting string is '%A, %B %d, %Y'. To change the default, simply modify the DFTFORMAT local variable in the GetDateString() procedure.

Note that in this procedure, I use three different LIKE keywords. The reference fields being specified are all declared in the RPGTIME source member (Figure 2), as is the prototype for the strftime() function. Also note that I've located the /COPY statement inside the GetDateString() procedure itself. This causes the fields declared in the /COPY member to be declared as local variables. So there are not name collisions that occur.

Next issue, a report from the COMMON IT Education Conference in Nashville, Tennessee! I hope to see you there.

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$0.00 Raised:
$