PHPで和暦・日本語曜日を使う DateTime拡張版

[PHPで和暦・日本語曜日を使う](http://qiita.com/chiyoyo/items/da32649b0e04957856c1)のDateTimeクラス拡張版を作ってみました。
お好きな方をお使いください。
フォーマットに使えるものも変わりありません。

## 注意
明治５年以前は天保暦・寛政暦などが利用されており、現在のグレゴリオ暦と日付が一致していません。
そのため、和暦の年号(K,k)のオプションは明治５年以前では正しく出ない場合があります。
日付に関してもグレゴリオ暦のものが表示されるため、和暦としての表示と異なります。
「JK年n月j日」が正しく動作するのは明治６年以降となりますのでご注意ください。
例：西暦 1834年6月1日 => 実際の和暦 天保5年4月24日

※@tadsanさん、情報・ご指摘ありがとうございます。

## ソース
```php
<?php
namespace MyLibrary;

/**
 * 日本語対応DateTime拡張クラス
 * 
 * 元号、曜日などの日本語表記に対応したDateTime拡張クラス
 * 
 * @version 1.0.1
 * @author chiyoyo
 * @caution PHP5.2以上
 */
class DateTimeJp extends \DateTime
{
    // フォーマット定義追加
    const JP_DATE = 'JK年n月j日'; // 例:平成元年5月7日（明治5年以前は当時と異なる日付が出るので注意）
    const JP_TIME = 'Eg時i分s秒'; // 例:午後3時05分07秒

    const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; // toString()で利用する表示フォーマット

    /**
     * 元号用設定
     * 日付はウィキペディアを参照しました
     * 
     * @see http://ja.wikipedia.org/wiki/%E5%85%83%E5%8F%B7%E4%B8%80%E8%A6%A7_%28%E6%97%A5%E6%9C%AC%29 元号一覧 (日本)
     */
    private static $gengoList = [
        ['name' => '令和', 'name_short' => 'R', 'timestamp' =>  1556636400],  // 2019-05-01,
        ['name' => '平成', 'name_short' => 'H', 'timestamp' =>  600188400],  // 1989-01-08,
        ['name' => '昭和', 'name_short' => 'S', 'timestamp' => -1357635600], // 1926-12-25'
        ['name' => '大正', 'name_short' => 'T', 'timestamp' => -1812186000], // 1912-07-30
        ['name' => '明治', 'name_short' => 'M', 'timestamp' => -3216790800], // 1868-01-25
    ];

    /** 日本語曜日設定 */
    private static $weekJp = [
        0 => '日',
        1 => '月',
        2 => '火',
        3 => '水',
        4 => '木',
        5 => '金',
        6 => '土',
    ];
    /** 午前午後 */
    private static $ampm = [
        'am' => '午前',
        'pm' => '午後',
    ];

    /**
     * 文字列に変換された際に返却するもの
     *
     * @return string
     */
    public function __toString()
    {
        return $this->format(self::DEFAULT_TO_STRING_FORMAT);
    }

    /**
     * 和暦などを追加したformatメソッド
     *
     * 追加した記号
     * J : 元号
     * b : 元号略称
     * K : 和暦年(1年を元年と表記)
     * k : 和暦年
     * x : 日本語曜日(0:日-6:土)
     * E : 午前午後
     * 
     * @param string $format DateTime::formatに準ずるformat文字列
     * @return string
     */
    public function format($format)
    {
        // 和暦関連のオプションがある場合は和暦取得
        $gengo = array();
        if (preg_match('/(?<!\\\)[J|b|K|k]/', $format)) {
            foreach (self::$gengoList as $g) {
                if ($g['timestamp'] <= $this->getTimestamp()) {
                    $gengo = $g;
                    break;
                }
            }
            // 元号が取得できない場合はException
            if (empty($gengo)) {
                throw new Exception('Can not be converted to a timestamp : '.$this->getTimestamp());
            }
        }

        // J : 元号
        if ($this->isCharactor('J', $format)) {
            $format = $this->replaceCharactor('J', $gengo['name'], $format);
        }

        // b : 元号略称
        if ($this->isCharactor('b', $format)) {
            $format = preg_replace('/b/', '¥¥' . $gengo['name_short'], $format);
        }

        // K : 和暦用年(元年表示)
        if ($this->isCharactor('K', $format)) {
            $year = date('Y', $this->getTimestamp()) - date('Y', $gengo['timestamp']) + 1;
            $year = $year == 1 ? '元' : $year;
            $format = $this->replaceCharactor('K', $year, $format);
        }

        // k : 和暦用年
        if ($this->isCharactor('k', $format)) {
            $year = date('Y', $this->getTimestamp()) - date('Y', $gengo['timestamp']) + 1;
            $format = $this->replaceCharactor('k', $year, $format);
        }

        // x : 日本語曜日
        if ($this->isCharactor('x', $format)) {
            $w = date('w', $this->getTimestamp());
            $format = $this->replaceCharactor('x', self::$weekJp[$w], $format);
        }

        // 午前午後
        if ($this->isCharactor('E', $format)) {
            $a = date('a', $this->getTimestamp());
            $value = isset(self::$ampm[$a]) ? self::$ampm[$a] : '';
            $format = $this->replaceCharactor('E', $value, $format);
        }

        return parent::format($format);
    }

    /**
     * 指定した文字があるかどうか調べる（エスケープされているものは除外）
     * @param string $char 検索する文字
     * @param string $string 検索される文字列
     * @return boolean
     */
    private function isCharactor($char, $string)
    {
        return preg_match('/(?<!\\\)'.$char.'/', $string);
    }

    /**
     * 指定した文字を置換する(エスケープされていないもののみ)
     * @param string $char 置換される文字
     * @param string $replace 置換する文字列
     * @param string $string 元の文字列
     * @return string 置換した文字列
     */
    private function replaceCharactor($char, $replace, $string)
    {
        // エスケープされていないもののみ置換
        $string = preg_replace('/(?<!\\\)'.$char.'/', '${1}'.$replace, $string);
        // エスケープ文字を削除
        $string = preg_replace('/\\\\'.$char.'/', $char, $string);
        return $string;
    }
}
```

## 参考
[生活や実務に役立つ高精度計算サイト](http://keisan.casio.jp/menu/system/000000000230)

## おまけ

利用できない元号をおまけとして載せてみます。
実際は天保歴などを計算しなければならないため、タイムスタンプが異なります。
以下は現在の暦から計算しただけのタイムスタンプです。

```php:明治以前の元号
    private static $gengoList = [
        // 江戸時代
        ['name' => '慶応', 'timestamp' => -3303104400], // 1865-05-01
        ['name' => '元治', 'timestamp' => -3337664400], // 1864-03-27
        ['name' => '文久', 'timestamp' => -3432186000], // 1861-03-29
        ['name' => '万延', 'timestamp' => -3462858000], // 1860-04-08
        ['name' => '安政', 'timestamp' => -3627882000], // 1855-01-15
        ['name' => '嘉永', 'timestamp' => -3842154000], // 1848-04-01
        ['name' => '弘化', 'timestamp' => -3943933200], // 1845-01-09
        ['name' => '天保', 'timestamp' => -4384573200], // 1831-01-23
        ['name' => '文政', 'timestamp' => -4784173200], // 1818-05-26
        ['name' => '文化', 'timestamp' => -5231552400], // 1804-03-22
        ['name' => '享和', 'timestamp' => -5326506000], // 1801-03-19
        ['name' => '寛政', 'timestamp' => -5707530000], // 1789-02-19
        ['name' => '天明', 'timestamp' => -5954374800], // 1781-04-25
        ['name' => '安永', 'timestamp' => -6218586000], // 1772-12-10
        ['name' => '明和', 'timestamp' => -6485130000], // 1764-06-30
        ['name' => '宝暦', 'timestamp' => -6881014800], // 1751-12-14
        ['name' => '寛延', 'timestamp' => -6986941200], // 1748-08-05
        ['name' => '延享', 'timestamp' => -7123885200], // 1744-04-03
        ['name' => '寛保', 'timestamp' => -7217802000], // 1741-04-12
        ['name' => '元文', 'timestamp' => -7370730000], // 1736-06-07
        ['name' => '享保', 'timestamp' => -7996438800], // 1716-08-09
        ['name' => '正徳', 'timestamp' => -8159389200], // 1711-06-11
        ['name' => '宝永', 'timestamp' => -8385066000], // 1704-04-16
        ['name' => '元禄', 'timestamp' => -8873485200], // 1688-10-23
        ['name' => '貞享', 'timestamp' => -9017082000], // 1684-04-05
        ['name' => '天和', 'timestamp' => -9092941200], // 1681-11-09
        ['name' => '延宝', 'timestamp' => -9346266000], // 1673-10-30
        ['name' => '寛文', 'timestamp' => -9738781200], // 1661-05-23
        ['name' => '万治', 'timestamp' => -9825699600], // 1658-08-21
        ['name' => '明暦', 'timestamp' => -9928602000], // 1655-05-18
        ['name' => '承応', 'timestamp' => -10009818000], // 1652-10-20
        ['name' => '正保', 'timestamp' => -10254934800], // 1645-01-13
        ['name' => '寛永', 'timestamp' => -10909501200], // 1624-04-17
        ['name' => '元和', 'timestamp' => -11181402000], // 1615-09-05
        // 安土桃山時代
        ['name' => '慶長', 'timestamp' => -11772118800], // 1596-12-16
        ['name' => '文禄', 'timestamp' => -11896189200], // 1593-01-10
        ['name' => '天正', 'timestamp' => -12507728400], // 1573-08-25
        // 戦国時代
        ['name' => '元亀', 'timestamp' => -12610198800], // 1570-05-27
        ['name' => '永禄', 'timestamp' => -12994938000], // 1558-03-18
        ['name' => '弘治', 'timestamp' => -13069414800], // 1555-11-07
        ['name' => '天文', 'timestamp' => -13801222800], // 1532-08-29
        ['name' => '享禄', 'timestamp' => -13927021200], // 1528-09-03
        ['name' => '大永', 'timestamp' => -14146218000], // 1521-09-23
        ['name' => '永正', 'timestamp' => -14699178000], // 1504-03-16
        ['name' => '文亀', 'timestamp' => -14793699600], // 1501-03-18
        ['name' => '明応', 'timestamp' => -15064909200], // 1492-08-12
        ['name' => '延徳', 'timestamp' => -15156579600], // 1489-09-16
        ['name' => '長享', 'timestamp' => -15223021200], // 1487-08-09
        ['name' => '文明', 'timestamp' => -15796371600], // 1469-06-08
        ['name' => '応仁', 'timestamp' => -15864714000], // 1467-04-09
    ];
```

## 更新履歴
| 日付 | 更新内容 |
|:--|:--|
| 2015/04/14 | エスケープすることで置換しないように修正 |
| 2016/01/15 | 日本語曜日出力のバグ修正 |