how to apply leap year check logic in cobol - cobol

I am new to COBOL and hence not familiar to all the control logic as it is quite different from other high level languages. I am having trouble in checking the date parameter while reading it from an given input file for a leap year condition. Can anyone please give the code for this?

Here is one method WS-YYYY can be defined as 9(4) DISPLAY, S9(4) DISPLAY, or S9(4) COMP. The other variables are all defined in WORKING-STORAGE as S9(4) COMP.
DIVIDE WS-YYYY BY 4 GIVING YEAR-BY-4 REMAINDER YEAR-BY-4-REMAINDER.
DIVIDE WS-YYYY BY 100 GIVING YEAR-BY-100 REMAINDER YEAR-BY-100-REMAINDER.
DIVIDE WS-YYYY BY 400 GIVING YEAR-BY-400 REMAINDER YEAR-BY-400-REMAINDER.
IF YEAR-BY-400-REMAINDER = 0
// LEAP YEAR CODE
ELSE IF YEAR-BY-100-REMAINDER = 0
// NON-LEAP YEAR CODE
ELSE IF YEAR-BY-4-REMAINDER = 0
// LEAP YEAR CODE
ELSE
// NON-LEAP YEAR CODE
. (period)

Related

What does COMP VALUE ZERO in COBOL mean?

There is a great explanation for COMP under the following stackoverflow link:
https://stackoverflow.com/a/42423487/7802354
but I still don't understand what COMP VALUE ZERO in
77 ABC PIC S9(4) COMP VALUE ZERO.
means. I would appreciate if someone clarifies that.
COMP or COMPUTATIONAL refers to the storage representation. For most implementations, it is the same as (big-endian) BINARY, for some PACKED-DECIMAL, rarely it is the same
storage representation as DISPLAY. VALUE ZERO means that the initial value will be +0.
It is similar to
short abc = 0;
in some other languages. However the PIC S9(4) limits the value to -9999 through +9999.
The way to read that line of code
77 ABC PIC S9(4) COMP VALUE ZERO.
is
77 is the level
ABC is the data item name
PIC S9(4) COMP is the data item's 'data type'
VALUE ZERO is what to initialize the data item to.

copybook misalignment in fileaid

I am trying to create a copybook structure for my data file.
Part of the data looks like this
C 0000.00
Since it has 0000.00 , in my copybook, we declared it as a PIC 9(04)v9(02).
but when i map it using fileaid, i get this error
15 EF-PURCH-FEE-AMT 6/AN 0000.0
15 EF-FILLER4 975/AN 0
The decimal point is considered as another byte and the last zero is spilling into the subsequent field
I have tried to define the picture clause as zoned decimal by giving value as PIC ZZZ9V99 as well. But its still spilling into next field.
Expected result. :
15 EF-PURCH-FEE-AMT 6/AN 0000.00
15 EF-FILLER4 975/AN 0
Actual result:
15 EF-PURCH-FEE-AMT 6/AN 0000.0
15 EF-FILLER4 975/AN 0
PIC definition as of now:
15 EF-PURCH-FEE-AMT PIC ZZZ9V99.
15 EF-FILLER4 PIC X(975).
Please refer to the documentation for the PICTURE clause of a data definition. There you will find that the V is a presumed or virtual decimal point, not a physical one. You may be able to attain your desired result with...
15 EF-PURCH-FEE-AMT PIC 9999.99.

Check a variable to make certain that it is all numbers?

How can I check Work-Trig to make sure that it is all digits?
code:
Work-Trig is --> 20140101
CHECK-TRIG.
IF WORK-TRIG IS NUMERIC THEN
MOVE "FALSE" TO ERR-TRIG
ELSE
MOVE "TRUE" TO ERR-TRIG
END-IF.
DISPLAY 'ERR-TRIG' ERR-TRIG.
X-CHECK. EXIT.
01 WORK-TRIG.
05 TRIG-YEAR PIC X(08) VALUE SPACES.
05 TRIG-MONTH PIC X(01) VALUE SPACES.
05 TRIG-DAY PIC X(01) VALUE SPACES.
05 FILLER PIC X(70) VALUE SPACES.
The problem is that WORK-TRIG is 80 bytes long. The first eight bytes contain your data, but the entire 80 bytes will be tested for being NUMERIC.
You have a data-name for the first eight bytes. If you test that instead of the group-item, your code will work.
CHECK-TRIG.
IF WORK-YEAR IS NUMERIC THEN
MOVE "FALSE" TO ERR-TRIG
ELSE
MOVE "TRUE" TO ERR-TRIG
END-IF.
DISPLAY 'ERR-YEAR' ERR-TRIG.
X-CHECK. EXIT.
If you have a data-name called WORK-YEAR, it should only contain a year. It should not contain an entire date. The point of good names for data is so that we, humans, can read and understand your code better. When looking for a problem, we find WORK-YEAR as eight bytes long, and have to spend time finding out if that is the correct length, or the correct name and a wrong length.
Given the code change, it would be good to use a different name for ERR-TRIG as well.
There are more obscure ways to test the first eight bytes of a group item, but since you already had a name, hopefully we'll keep reference-modification out of this one.
The following code example works and will check each position in you WORK-TRIG1 to see if that value is a NUMERIC. I tested this and it does work. This example uses a PERFORM VARYING loop to index through each "location" in the string to see if it is valid.
Should Work ALSO:
IF A IS NUMERIC THEN
//code here
END-IF
I do know that the below code works because I took it directly from a program that is running perfectly and has since 88 or 89.
Code:
CHECK-TRIG.
PERFORM VARYING SUB1 FROM 1 BY 1 UNTIL SUB1 > 8
IF WORK-TRIG(1:SUB1) IS NUMERIC THEN
MOVE 'FALSE' TO ERR-TRIG
ELSE
MOVE 'TRUE' TO ERR-TRIG
MOVE SUB1 TO SV-RTN-CODE
MOVE 9 TO SUB1
END-IF
END-PERFORM.
X-CHECK. EXIT.
--Code--New this does not work
PERFORM VARYING SUB1 FROM 1 BY 1 UNTIL SUB1 > 8
IF WORK-TRIG(SUB1:8) IS NUMERIC THEN
MOVE 'FALSE' TO ERR-TRIG
ELSE
MOVE 'TRUE' TO ERR-TRIG
MOVE SUB1 TO SV-RTN-CODE
MOVE ' TRIGGER CARD ERROR (SEE DATE BELOW)' TO
ERR-DET
MOVE 9 TO SUB1
END-IF
END-PERFORM.
Recently had experienced non-numeric being successfully processed into a numeric (COMP-3) field.
Value of 'FALSE99999999' moved into a COMP-3 field and became 6132599999999 and the program never encounter an S0C7 ABEND.
Looks like verifying each byte of the source field is the only way to identify such issue.

Any date should be converted to end of the month date in cobol?

I have a requirement where any date (DD.MM.YYYY) should be converted to last date of month (ex: If date is 20.01.1999 then it should convert into 31.01.1999) ?
Exactly what are you having trouble with? COBOL or the algorithm? I'm guessing its COBOL.
I'm not going to give you a direct answer because you are
obviously leaning the language and there is value in working out the specific details
for yourself.
Here are a couple of hints:
Define a date field in WORKING-STORAGE so that you can pick out the day, month and year as separate items. Something like:
01 TEST-DATE.
05 TEST-DAY PIC 99.
05 PIC X.
05 TEST-MONTH PIC 99.
05 PIC X.
05 TEST-YEAR PIC 9999.
Note the unnamed PIC X fields. These contain the day/month/year delimiters. They do not need to be given data names because
you do not need to reference them. Sometimes this type of data item is given
the name FILLER, but the name is optional.
Read up on the EVALUATE statement. Here is a link to
the IBM Enterprise COBOL manual. This description of EVALUATE should be similar in all versions of COBOL.
MOVE the date of interest TO TEST-DATE. Now you can reference the year, month and day as individual items: TEST-DAY, TEST-MONTH and TEST-YEAR.
Use EVALUATE to test the month (TEST-MONTH). If the month is a 30 day month then MOVE 30 to TEST-DAY. Do the same for
31 day months. February is a special case because of leap years. Once you have determined that the month is February,
test TEST-YEAR to determine if it is a leap year
and MOVE 28 or 29 TO TEST-DAY depending on the outcome of the test.
Now TEST-DATE will contain the date you are looking for. MOVE it to wherever it is needed.
You can use function integer-of-date which gives returns an integral value corresponding to any date. Assuming your input date is in ddmmyyyy format and you expect hte output in the same format. Lets say date is 20011999 and you want as 31011999. You can follow the below steps.
Increase the month of the input date by one. (20*02*1999)
Make the day as 01 and use function integer-of-date (*01*021999)
subtract one from the integer returned.
use function date-of-integer which will give you the required result.
Note here you will have to add one more check for handling December month.
Here you go! Run the code here
IDENTIFICATION DIVISION.
PROGRAM-ID. STACK2.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DATE PIC X(10).
01 WORK-DATE.
05 WORK-DAY PIC 9(2).
05 PIC X.
05 WORK-MONTH PIC 9(2).
05 PIC X.
05 WORK-YEAR PIC 9(4).
01 MONTH-31 PIC 9(2).
88 IS-MONTH-31 VALUES 01, 03, 05, 07, 08, 10, 12.
88 IS-MONTH-30 VALUES 04, 06, 09, 11.
01 WS-C PIC 9(4) VALUE 0.
01 WS-D PIC 9(4) VALUE 0.
PROCEDURE DIVISION.
ACCEPT WS-DATE.
MOVE WS-DATE TO WORK-DATE.
DISPLAY 'ACTUALE TEST-DATE: ' WORK-DATE.
MOVE WORK-MONTH TO MONTH-31.
EVALUATE TRUE
WHEN IS-MONTH-31
MOVE 31 TO WORK-DAY
WHEN IS-MONTH-30
MOVE 30 TO WORK-DAY
WHEN OTHER
DIVIDE WORK-YEAR BY 4 GIVING WS-C REMAINDER WS-D
IF WS-D NOT EQUAL 0
MOVE 28 TO WORK-DAY
ELSE
MOVE 29 TO WORK-DAY
END-IF
END-EVALUATE.
DISPLAY 'MODIFIED TEST-DATE: ' WORK-DATE
STOP RUN.
This solution goes hand in hand with #NealB's answer.
The procedure "compute-month-end-date" does not require any checks for leap year or December.
identification division.
program-id. last-day.
data division.
working-storage section.
1 test-date.
88 test-1 value "20.01.1999".
88 test-2 value "20.02.2004".
88 test-3 value "20.12.2005".
2 dd pic 99.
2 pic x.
2 mm pic 99.
2 pic x.
2 yyyy pic 9999.
1 month-end-date binary pic 9(8) value 0.
procedure division.
begin.
set test-1 to true
perform run-test
set test-2 to true
perform run-test
set test-3 to true
perform run-test
stop run
.
run-test.
display test-date " to " with no advancing
perform test-date-to-iso
perform compute-month-end-date
perform iso-to-test-date
display test-date
.
compute-month-end-date.
*> get date in following month
compute month-end-date = function
integer-of-date (month-end-date) + 32
- function mod (month-end-date 100)
compute month-end-date = function
date-of-integer (month-end-date)
*> get last day of target month
compute month-end-date = function
integer-of-date (month-end-date)
- function mod (month-end-date 100)
compute month-end-date = function
date-of-integer (month-end-date)
.
test-date-to-iso.
compute month-end-date = yyyy * 10000
+ mm * 100 + dd
.
iso-to-test-date.
move month-end-date to dd
.
end program last-day.
Results:
20.01.1999 to 31.01.1999
20.02.2004 to 29.02.2004
20.12.2005 to 31.12.2005
While this might be a bit daunting to the novice COBOL programmer, there is a simple explanation. The procedure "compute-month-end-date" consists of two identical parts with the exception of the "+32".
Taking the second part first, it subtracts the day of month from the integer of a date giving the integer value for the 'zeroth' day of the month. This is precisely the integer value for the last day of the prior month. The following compute gives the date in 'yyyymmdd' format.
The first part does the same, except that it adds 32 to get a date in the following month, the 1st through the 4th, depending on the number of days in the original month.
Taken togther 19990120 is first changed to 19990201, then changed to 19990131. And 20040220 to 20040303, then 20040229. 20051220 to 20060101, then 20051231.

How to convert a alphanumeric string into numeric decimal in COBOL

For eg., i have alphanumeric string 'ABCDEF 0 0.450' and i need to get '0.450' as numeric decimal and do arithmetic on it. Do we have a way? Please suggest.
I assume the format is not fixed, since then you could just pick that part of the string and move it to a field defined with pic 9.999 and use it from there.
This assumes the string value is made up of 3 parts separated by spaces. First define some fields:
1 part1 pic x(14).
1 part2 pic x(14).
1 part3 pic x(07).
1 digitsAn.
2 digits pic 9(03).
1 decimalsAn.
2 decimals pic .9(03).
1 theValue pic 9(03)v9(3).
The An suffix is from my naming convention, and indicates a alphanumeric value. If the string input may be longer increase the sizes as needed.
The rest of the code parses theString into theValue.
* Extract the third part.
initialize part3
unstring theString delimited by all spaces into part1 part2 part3
* make sure the receiving field contains numeric data.
inspect part3 replacing spaces by zeroes
* extract digits before and after the decimal point.
unstring part3 delimited by "." into digits decimalsAn(2:)
* Combine parts before and after decimal points into a numeric value.
compute theValue = digits + decimals
Beware, I haven't run this through a compiler!
Asusually, I could find out a way to achieve it!
As said above, UNSTRINGing and combining didnt work, but REDEFINES works!
Get alphanumeric string redefined into two numeric fields to hold and process decimal part and integer part individually. Atlast divide the decimal-total by 1000 and add it to integer-part. Thats it! Code snippets follow...
01 WS-NUM-STR PIC X(14) VALUE 'ABCDEF 0 0.450'.
01 WS-NUM-STR-MOD REDEFINES WS-NUM-STR.
05 FILLER PIC X(9).
05 WS-INT-PART PIC 9.
05 FILLER PIC X.
05 WS-DEC-PART PIC 999.
----------
----------
* CODE TO ADD ALL INTEGER PARTS
* CODE TO ADD ALL DECIMAL PARTS
COMPUTE = INTEGER-TOTAL + (DECIMAL-TOTAL / 1000)
Note: As I already knew that decimal part has 3 digits, I divided DECIMAL-TOTAL by 1000. Hope you could figure out.
And Please suggest any other way to achieve the same!
In most modern Cobol compilers, you can move a numeric-edited field to a numeric field.
Borrowing from your example below:
01 WS-NUM-STR PIC X(14) VALUE 'ABCDEF 0 0.450'.
01 WS-NUM-STR-MOD REDEFINES WS-NUM-STR.
05 FILLER PIC X(9).
05 WS-EDITED-NUMBER PIC 9.999.
01 WS-NUMBER PIC 9V999.
----------
MOVE WS-EDITED-NUMBER TO WS-NUMBER
And then, do the calculation with WS-NUMBER.
For eg., i have alphanumeric string
'ABCDEF 0 0.450' and i need to get
'0.450' as numeric decimal and do
arithmetic on it. Do we have a way?
Please suggest.
If you are running in CICS, you could use the BIF DEEDIT function -- that will leave you with "00.450".
If you are not running in CICS, assume you have your string in a field called WS-STR:
Move 0 to JJ
Perform varying II from 1 by 1
until II > Length of WS-STR
Evaluate WS-STR (II:1)
when '0'
when '1'
when '2'
when '3'
when '4'
when '5'
when '6'
when '7'
when '8'
when '9'
when '.'
Add 1 to JJ
Move WS-STR (II:1) to WS-NUM (JJ:1)
* ---- Do nothing
when other
continue
End-Perform
Compute REAL-DEC-NUM = Function NUM-VAL (WS-NUM)

Resources