PHPで和暦・日本語曜日を使う

和暦と日本語の曜日を表示する時に、毎回同じようなコードを書いている気がするので、使い勝手のいい感じにまとめてみた。
改良の余地は色々あるけど、まぁまぁ使えるかなぁと。
※[DateTimeクラス拡張版](http://qiita.com/chiyoyo/items/3d12e5b1ef63e7f332ba)も作ってみました

# 説明
```php
string DatetimeUtility::date(string $format[, int $timestamp = time()])
```
[date](http://php.net/manual/ja/function.date.php)関数を拡張した関数。

## 追加format文字
| format文字 | 説明 | 戻り値の例 |
|:--|:--|:--|
|J|元号(明治より前はありません)|令和,平成,昭和,大正,明治,...|
|b|元号略称(明治より前はありません)|R,H,S,T,M|
|K|元号に対応した年(1年を元年と表記)|元,2,3...|
|k|元号に対応した年|1,2,3...|
|x|日本語の曜日|日から土|
|E|AM,PMの日本語|午前,午後|
|p|時。12時間単位。先頭にゼロを付けない。|0から11|
|q|時。数字。12 時間単位。|00から11|

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

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

# 更新履歴
| 日時 | 更新内容 |
|:--|:--|:--|
| 2015/04/13 | 午前午後を追加しました |
| 2015/04/13 | 明治より前の元号に対応しました |
| 2015/04/14 | kとKのオプションが逆になっているバグ修正しました |
| 2019/04/01 | 新元号「令和」を追加しました |
| 2019/04/02 | 日本用の0〜11時のpとqを追加しました |

# ソース
```php
<?php
/**
 * 日時用汎用クラス
 */
class DatetimeUtility {
    /** 元号用設定
     * 日付はウィキペディアを参照しました
	 * 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' => '午後',
    ];

    /**
     * 和暦などを追加したdate関数
     *
     * 追加した記号
     * J : 元号
     * b : 元号略称
     * K : 和暦年(1年を元年と表記)
     * k : 和暦年
     * x : 日本語曜日(0:日-6:土)
     * E : 午前午後
     */
    public static function date($format, $timestamp = null)
    {
        // 和暦関連のオプションがある場合は和暦取得
        $gengo = array();
        $timestamp = is_null($timestamp) ? time() : $timestamp;
        if (preg_match('/[J|b|K|k]/', $format)) {
            foreach (self::$gengoList as $g) {
                if ($g['timestamp'] <= $timestamp) {
                    $gengo = $g;
                    break;
                }
            }
            // 元号が取得できない場合はException
            if (empty($gengo)) {
                throw new Exception('Can not be converted to a timestamp : '.$timestamp);
            }
        }

        // J : 元号
        if (strpos($format, 'J') !== false) {
            $format = preg_replace('/J/', $gengo['name'], $format);
        }

        // b : 元号略称
        if (strpos($format, 'b') !== false) {
            $format = preg_replace('/b/', '¥¥' . $gengo['name_short'], $format);
        }

        // K : 和暦用年(元年表示)
        if (strpos($format, 'K') !== false) {
            $year = date('Y', $timestamp) - date('Y', $gengo['timestamp']) + 1;
            $year = $year == 1 ? '元' : $year;
            $format = preg_replace('/K/', $year, $format);
        }

        // k : 和暦用年
        if (strpos($format, 'k') !== false) {
            $year = date('Y', $timestamp) - date('Y', $gengo['timestamp']) + 1;
            $format = preg_replace('/k/', $year, $format);
        }

        // x : 日本語曜日
        if (strpos($format, 'x') !== false) {
            $w = date('w', $timestamp);
            $format = preg_replace('/x/', self::$weekJp[$w], $format);
        }

        // 午前午後
        if (strpos($format, 'E') !== false) {
            $a = date('a', $timestamp);
            $format = preg_replace('/E/', self::$ampm[$a], $format);
        }

        // 時。12時間単位。先頭にゼロを付けない。(0-11)
        if (strpos($format, 'p') !== false) {
            $hour = date('g', $timestamp);
            $hour = $hour == 12 ? 0 : $hour;
            $format = preg_replace('/p/', $hour, $format);
        }

        // 時。数字。12 時間単位。(00-11)
        if (strpos($format, 'q') !== false) {
            $hour = date('h', $timestamp);
            $hour = str_pad($hour == 12 ? 0 : $hour, 2, '0');
            $format = preg_replace('/q/', $hour, $format);
        }

        return date($format, $timestamp);
    }

}
```

## おまけ

明治以前をおまけとして載せてみる。
実際は天保歴などを加味してタイムスタンプを変える必要があります。
記載しているものは単純に現在の暦から計算しただけのものです。

```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
    ]
```