Unpack Mainframe packed Decimal (BCD) with PHP

1.3k Views Asked by At

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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

6
On

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.