使用 PHP 解压大型机压缩十进制 (BCD)

Unpack Mainframe packed Decimal (BCD) with PHP

我从大型机上得到了一个数据文件。我已经使用 PHP 处理了 EBCDIC 到 latin1 的转换。但是现在还剩下这个压缩的十进制字段。

例如,数字 12345 被打包成 3 个字节,看起来像:x'12345C'

负值类似于:x'12345D'

所以右半字节告诉标志。有没有办法用 PHP 轻松做到这一点?

现在我这样做:

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

结果是:

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

现在我可以检查最后一个符号是 C 还是 D,然后全部手动完成。但也许有更好的解决方案?

正如 Bill 所说,让大型机人员将文件转换为 大型机上的文本 并发送文本文件,排序等实用程序可以在大型机上执行此操作。它也是只是在文件中打包了十进制还是你有二进制Zoned Decimal???

如果您坚持在 PHP 中执行此操作,则需要在 进行 EBCDIC 转换之前 进行压缩十进制转换,因为对于像 x 这样的压缩十进制'400c' EBCDIC 转换器将查看 x'40' 并说这是一个 space 并将其转换为 x'20',因此您的 x'400c' 变为 x'200c'。

此外,压缩十进制中的最后一个 nyble 可以是 f - unsigned 以及 c 和 d。

最后,如果你有 Cobol Copybook,我的项目 JRecord 有 Cobol 到 Csv && Cobol 到 Xml 的转换程序(写在 java 中)。参见

好的,因为我没有找到任何更好的解决方案,所以我做了一个 php-class 来处理来自这个数据集的记录:

<?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;
    }
}

使用 $class->read(1, 3, True) 可以同时读取部分数据和 convert/unpack。

也许它也会随时帮助任何人。

但我当然会尝试设置一些 Job,它会直接在大型机上为我完成这些工作,并输出一些 JSON 数据。