TechTip: Date Arithmetic in CL Programs

  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

Date arithmetic in CL programs is limited and lacks the simplicity and scope found in RPG. This article provides a command that brings RPG date capabilities to CL programs.

Date handling and arithmetic in RPG is superb, capable, and straightforward. (I've written about it before in Calculate Begin and End Dates for Any Month and here in RPG IV Legacy Dates Cheat Sheet). But when it comes to dates in a CL program, there are limited capabilities, and you need more coding.

If you search the web for "date arithmetic in CL programs," you will find numerous hints and approaches. Even IBM provides an example program. But none are as simple or robust as what RPG provides. To reduce the complexity, I offer the DATEADJ command to add the functionality of RPG date handling to CL programs.

The DATEADJ Command

DATEADJ adjusts a date forward or backward by days, months, or years and returns an adjusted date. Input and output dates can be in almost any format that RPG recognizes.

A prompted command looks like this:

TechTip: Date Arithmetic in CL Programs - Figure 1 

Figure 1: The prompted DATEADJ command.

Input Date can be *JOBDATE to use the job date, or *SYSTEM to use the system date, or a character date value, e.g., 12/31/21.

Output Date is where the adjusted date is returned.

Amount to adjust by is the adjustment amount: positive to add to the date or negative to subtract from the date. Zero is also allowed, which allows you to simply reformat a date.

Adjustment type defines the type of the adjustment amount: *DAYS, *MONTHS, or *YEARS.

Input Date Format specifies the format of the input date. It allows nearly all the date formats that RPG allows: *YMD, *MDY, *DMY, *ISO, *JUL, etc., with or without delimiters. It also allows *JOBFMT for an input date in job format and *SYSTEM for an input date in system format. (Input date format does not apply if Input Date is *JOBDATE or *SYSTEM, since these are predefined by the operating system.)

Output Date Format defines the format of the returned date. It allows all the input formats, plus it defaults to *INFMT to specify the same format as the input date.

Simple DATEADJ Usage Examples

/* Tomorrow */


/* Yesterday*/


/* Day before arbitrary date & reformat */

    DATEADJ    INDATE('2019-03-21') OUTDATE(&NEWDTE) +

                ADJAMT(-1) INFMT(*ISO) OUTFMT(*JOBFMT)


Complex DATEADJ Usage Example

/* Calculate last month beginning and ending dates */


    DCL        VAR(&ADJ) TYPE(*CHAR) LEN(3)

    DCL        VAR(&WKDATE) TYPE(*CHAR) LEN(10)

    DCL        VAR(&EOML) TYPE(*CHAR) LEN(10)

    DCL        VAR(&BOML) TYPE(*CHAR) LEN(10)

/* Adjustment is minus day value of today in system date*/



/* Last day of last month */


/* 1st day of this month */


/* 1st day of last month */


                ADJTYPE(*MONTHS) INFMT('*SYSTEM')

    SNDMSG     MSG('Last month is' *BCAT &BOML *BCAT +

                'through' *BCAT &EOML) TOUSR(*REQUESTER)

How It Works

The Command processing program (CPP) for DATEADJ is an RPG program, DATEADJR, that leverages the RPG date handling capabilities.

DATEADJR does some setup code to take care of the default special values, converts the input date to a date data type, does the arithmetic, and finally converts the adjusted date to the desired output format.

The input date conversion is a long SELECT statement that starts like this:


    when (inFmt = '*YMD');

      outDate = %date(inChar : *YMD);

    when (inFmt = '*MDY');

      outDate = %date(inChar : *MDY);

    when (inFmt = '*DMY');

      outDate = %date(inChar : *DMY);

The output date conversion is the inverse, and it starts like this:


    when (inFmt = '*YMD');

      outDate = %date(inChar : *YMD);

    when (inFmt = '*MDY');

      outDate = %date(inChar : *MDY);

    when (inFmt = '*DMY');

      outDate = %date(inChar : *DMY);

All the date conversions are wrapped in MONITOR groups. Invalid dates or adjustments resulting in overflow or underflow return a CPF9898 *ESCAPE message.

Coding Comments

DATEADJR is not a complex RPG program, and adding additional custom date formats should be easy. Probably the most complex part was ensuring that all the input and output formats were covered and each had a test case.

For completeness, I created a help panel group (DATEADJP.PNLGRP). If you want to add help to your own commands, I recommend starting with IBM's GENCMDDOC command to give you a basic outline. (If you want additional background on the UIM markup language used in help panels, it is hard to find. IBM pointed me to the Application Display Programming PDF.) While SEU will edit panel groups, it is vastly simpler to use a GUI editor. My current editor of choice is the free Code for IBM i extension to VS Code.

I doubt if execution speed is a consideration, because I see DATEADJ generally being executed only at the start of a job. That being said, my test program runs the command over 70 times, and on PUB400.COM it completed in under 1 second and used 0.041 processing units.


All the code can be downloaded from Alternatively, you can inspect or download the complete code at my GITHUB repository. If you find any bugs, or have any comments, or know any better way to provide this functionality to a CL program, I'd like to hear about it.