Suggestions
In general
- Change the Cobol. If the cobol was changed to
s9(6)V99 sign leading
it would be a lot easier to handle in java. In your case, this is probably not an option
- If you can get the Cobol Copybook use a package
Packages
If you can get the Cobol Copybook, why not use one of the Cobol / Java packages
Note: Even if you do not have the Full Cobol Copybook, you could set up a Cobol Copybook for this one field and still use a package. The copybook would be:
01 MY-REC.
03 FIELD-1 PIC S9(6)V99.
What you need to know
There is no one single Cobol Zoned Decimal format, it varies from compilere to Compiler and
what the encoding is. To decode Zoned Decimal
you really need to know the
- The Cobol Compiler
- The Encoding used on the Server
In this case I would guess it is
- IBM Compiler running on IBM Mainframe or AS400
- US Ebdic (IBM037) or something similar (definitely not German EBCDIC (IBM273))
Interpretting Zoned decimal
In Zoned decimal:
- S indicates it is a signed number; the sign will be
overpunched
on the last numeric digit.
- 9 represents a single digit
- V represents an
assumed
decimal place
So s9(6)V99
is a signed number with 6 digits before the decimal place + 2 after
Effect of Encoding
The encoding
(characterset) used by the server determines how the sign digit is represented.
For US (and UK) Ebcdic +0/-0 are { / }
but they are different for German Ebcdic.
For ASCII servers it is different again
Java Code
The ebcdic conversion code (note it still needs to be adjusted for the assumed decimal):
private static int positiveDiff = 'A' - '1';
private static int negativeDiff = 'J' - '1';
private static char positive0EbcdicZoned = '{';
private static char negative0EbcdicZoned = '}';
public static String fromZoned(String numZoned) {
String ret;
String sign = "";
char lastChar, ucLastChar;
if (numZoned == null || ((ret = numZoned.trim()).length() == 0) || ret.equals("-")) {
return "";
}
lastChar = ret.charAt(ret.length() - 1);
ucLastChar = Character.toUpperCase(lastChar);
switch (ucLastChar) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
lastChar = (char) (ucLastChar - positiveDiff);
break;
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
sign = "-";
lastChar = (char) (ucLastChar - negativeDiff);
break;
default:
if (lastChar == positive0EbcdicZoned) {
lastChar = '0';
} else if (lastChar == negative0EbcdicZoned) {
lastChar = '0';
sign = "-";
}
}
ret = sign + ret.substring(0, ret.length() - 1) + lastChar;
return ret;
}
To set +0/-0
characters
public static void setDefaultEbcidicCharacterset(String charset) {
if (getHold(charset).isEbcdic) {
byte[] b = {(byte) 0xC0, (byte) 0xD0};
String s = new String(b, charset);
if (s.length() == 2) {
positive0EbcdicZoned = s.charAt(0);
negative0EbcdicZoned = s.charAt(1);
}
}
}
An alternative way to derive the sign (for EBCDIC encoding) is to convert the sign back into the raw bytes:
private static final byte HIGH_NYBLE = (byte) 0xf0;
private static final byte ZONED_NEGATIVE_NYBLE_VALUE = (byte) 0xD0;
String Sign = "";
byte signByte = signStr.getBytes(encoding)[0];
if (((byte) (signByte & HIGH_NYBLE)) == ZONED_NEGATIVE_NYBLE_VALUE) {
sign = "-";
}
byte lastDigitBytes = (byte) (signByte | HIGH_NYBLE);
ASCII Cobols
In this case it is EBCDIC. For ASCII based cobols it is different again. This is JRecord generic conversion class for Ascii Zoned-Decimal:
Note: I am the author JRecord