4

I got an data file from a mainframe. I handled already the EBCDIC conversion to latin1 with PHP. But now are this packed decimal fields left.

For examle the number 12345 is packed into 3 Bytes and looks like: x'12345C'

Negative would be like: x'12345D'

So the right half Byte telling the sign. Is there a way to do this easily with PHP?

Now I do that like:

$bin = "\x12\x34\x5C";
var_dump(
    unpack("H*", $bin)
);

It results in:

array(1) {
  [1]=>
  string(4) "123c"
}

Now I could check if last sign is C or D and do all by hand. But maybe there is a nicer solution?

AbraCadaver
  • 78,200
  • 7
  • 66
  • 87
Andi S.
  • 317
  • 1
  • 11
  • 4
    The nicer solution is to get the Mainframe people to give you everything "text only". Then you get to do the code-set conversion at file/record level, and don't have any problems with anything. This means a "separate sign", and either a scaling factor or an actual decimal-point, whichever is easier for you to do. Unless the Mainframe program was coded in Assembler, it is trivial for them to produce your data like that, then it saves you having to do much at all. See also other questions tagged packed-decimal. – Bill Woodger Jun 23 '16 at 15:18
  • TL;DR "Mainframe. But maybe there is a nicer solution?" Yes. Let the dinosaur handlers figure it out instead. –  Jun 24 '16 at 00:25
  • 1
    @Rhymoid you meant TS:DR? There's no figuring out. Nada. Zero. None. You didn't need to come up with cool epithets to allow us to grasp that you have reading difficulties. Just write anything. – Bill Woodger Jun 24 '16 at 05:42
  • 2
    Bill's right - do the conversion on the mainframe. It could do it in the extra uptime whilst the toytown computers are down. – Steve Ives Jun 24 '16 at 09:00
  • Of course to do it on Mainframe is best way. but in my case I just had this files already on PC. It is a dump of dli database which is done every week. – Andi S. Jun 24 '16 at 14:56

2 Answers2

2

As Bill said, get the mainframe people to convert the file to Text on the Mainframe and send the Text file, utilities like sort etc can do this on the mainframe. Also is it just packed decimal in the file or do you have either binary or Zoned Decimal as well ???

If you insist on doing it in PHP, you need to do the Packed Decimal conversion before you do the EBCDIC conversion because for a Packed-decimal like x'400c' the EBCDIC converter will look at the x'40' and say that is a space and convert it to x'20', so your x'400c' becomes x'200c'.

Also the final nyble in a Packed-decimal can be f - unsigned as well as c and d.

Finally if you have the Cobol Copybook, my project JRecord has Cobol to Csv && Cobol to Xml conversion programs (Written in java). See

Community
  • 1
  • 1
Bruce Martin
  • 10,358
  • 1
  • 27
  • 38
1

Ok, because I did not find any nicer solution, I made a php-class for handle a record from this dataset:

<?php
namespace Mainframe;

/**
 * Mainframe main function
 *
 * @author vp1zag4
 *        
 */
class Mainframe
{

    /**
     * Data string for reading
     * 
     * @var string | null
     */
    protected $data = null;

    /**
     * Default ouput charset
     * 
     * @var string
     */
    const OUTPUT_CHARSET = 'latin1';

    /**
     * Record length of dataset
     *
     * @var integer
     */
    protected $recordLength = 10;

    /**
     * Inits the
     *
     * @param unknown $data            
     */
    public function __construct($data = null)
    {
        if (! is_null($data)) {
            $this->setData($data);
        }
    }

    /**
     * Sets the data string and validates
     *
     * @param unknown $data            
     * @throws \LengthException
     */
    public function setData($data)
    {
        if (strlen($data) != $this->recordLength) {
            throw new \LengthException('Given data does not fit to dataset record length');
        }

        $this->data = $data;
    }

    /**
     * Unpack packed decimal (BCD) from mainframe format to integer
     *
     * @param unknown $str            
     * @return number
     */
    public static function unpackBCD($str)
    {
        $num = unpack('H*', $str);
        $num = array_shift($num);
        $sign = strtoupper(substr($num, - 1));
        $num = (int) substr($num, 0, - 1);
        if ($sign == 'D') {
            $num = $num * - 1;
        }
        return (int) $num;
    }

    /**
     * convert EBCDIC to default output charset
     *
     * @param string $str            
     * @return string
     */
    public static function conv($str, $optionalCharset = null)
    {
        $charset = (is_string($optionalCharset)) ? $optionalCharset : self::OUTPUT_CHARSET;
        return iconv('IBM037', $charset, $str);
    }

    /**
     * Reads part of data string and converts or unpacks
     *
     * @param integer $start
     * @param integer $length
     * @param bool $unpack
     * @param bool | string $conv
     */
    public function read($start, $length, $unpack = false, $conv = true)
    {
        if (empty($this->data)) {
            return null;
        }

        $result = substr($this->data, $start, $length);

        if($unpack) {
            return self::unpackBCD($result);
        }

        if ($conv) {
            return self::conv($result, $conv);
        }

        return $result;
    }
}

With $class->read(1, 3, True) it is possible to read part of the data and convert/unpack it on same time.

Maybe it will help anybody anytime, too.

But of course I will try to setup some Job which will do that for me directly on mainframe with some JSON data as output.

Andi S.
  • 317
  • 1
  • 11
  • 1
    You're missing a few small tidbits - for instance, the sign nibble can be other values besides 0xC and 0xD, and I don't think you're handling them properly. You might want to review chapter 8 in the Principles of Operation (http://publibfp.dhe.ibm.com/epubs/pdf/dz9zr010.pdf) for all the gory details on packed decimal instructions. – Valerie R Jun 24 '16 at 21:26
  • I was imagining fixed-width text fields. If that is not easy for your language to deal with, then Enterprise COBOL V6.1 has native support for generating JSON, but V6.1 is fairly new. Very new, indeed. There is also a System Service to generate JSON (it s what the COBOL compiler uses). It adds overhead on both sides, but if it is better than you are able to handle fixed-width character fields, then it can be done. And yes, @ValerieR is correct. You are unlikely to see other than C, D or F, but what if you do? So you have to code for it, if you are going to code it out like the above. – Bill Woodger Jun 24 '16 at 21:31
  • I can't help thinking its easier not to have to do anything, but perhaps that kills your language (seems to be a problem with C#, it's just not very good at lots of fixed-width fields, because that means lots of string processing). – Bill Woodger Jun 24 '16 at 21:33
  • 2
    By the way, I'd be remiss if I didn't point out that IBM has most of the conversion routines you'd want in a portable Java component described here: https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.api.80.doc/com.ibm.dataaccess/com/ibm/dataaccess/DecimalData.html – Valerie R Jun 24 '16 at 21:45
  • @ValerieR Interesting, from the link "All values between 0x0 and 0xF inclusive are interpreted as valid sign codes." Whereas 0x0-0x9 would cause Mainframe decimal arithmetic to... choke. Where choke - abend | throw exception. I think "everything is a valid sign... until you do arithmetic" is a DFSORT thing as well. Probably means there are deeper roots. – Bill Woodger Jun 25 '16 at 07:41
  • Andi, this was really helpful!! Thanks for sharing the code. – Himan Jun 04 '20 at 21:14