【PHP】FormatDateオブジェクトのコード解説

Web開発

この前作成したFormatDateオブジェクトについて詳しいコード解説して!

前回こちらの記事にて発表させていただいた、FormatDtateクラスについて、詳しいコード解説をしていきたいと思います。

ポイント
  • 自分で日付フォーマットクラスを作成してみたい
  • FormatDateクラスのコードを詳しく知りたい

こんな方に読んで欲しい記事になっています。

まずは完成コードを載せて、そこから各パートでコードの説明をしていきます。

完成コード

<?php
class FormatDate 
{
  private $date_time;
  private $year;
  private $era_lists = [
    // 令和(2019年5月1日〜)
    [
      'jp' => '令和', 
      'jp_abbr' => '令',
      'en' => 'r',
      'en_abbr' => 'R',
      'time' => '20190501',
      'year' => '2019'
    ],
    // 平成(1989年1月8日〜)
    [
      'jp' => '平成',
      'jp_abbr' => '平',
      'en' => 'h',
      'en_abbr' => 'H',
      'time' => '19890108',
      'year' => '1989'
    ],
    // 昭和(1926年12月25日〜)
    [
      'jp' => '昭和',
      'jp_abbr' => '昭',
      'en' => 's',
      'en_abbr' => 'S',
      'time' => '19261225',
      'year' => '1926'
    ],
    // 大正(1912年7月30日〜)
    [
      'jp' => '大正',
      'jp_abbr' => '大',
      'en' => 't',
      'en_abbr' => 'T',
      'time' => '19120730',
      'year' => '1912'
    ],
    // 明治(1873年1月1日〜)
    // ※明治5年以前は旧暦を使用していたため、明治6年以降から対応
    [
      'jp' => '明治',
      'jp_abbr' => '明',
      'en' => 'm',
      'en_abbr' => 'M',
      'time' => '18730101',
      'year' => '1873'
    ],
  ];

  public function __construct($time = 'now', $timezone = null) {
    $time = preg_replace("/\\s| /", "", mb_convert_kana($time, 'n'));
    if(preg_match('/^(令|令和|r|R|平|平成|h|H|昭|昭和|s|S|大|大正|t|T|明|明治|m|M)?(元|0?[1-9])?(年|-|\\/?)?(0?[1-9]|1[0-2])?(月|-|\\/?)?(0?[1-9]|[12][0-9]|3[01])?(日?)?$/', $time, $matches)) {

      $era = $matches[1];
      $year = intval(str_replace('元', '1',$matches[2]));
      $this->year = sprintf('%02d', $year);
      $month = !empty($matches[4]) ? intval($matches[4]) : 1;
      $day = !empty($matches[6]) ? intval($matches[6]) : 1;

      foreach ($this->era_lists as $era_list) {
        if($era_list['jp'] === $era || $era_list['jp_abbr'] === $era || $era_list['en'] === $era || $era_list['en_abbr'] === $era){
          $seireki_year = $era_list['year'] + $year - 1;
          $seireki = sprintf('%04d-%02d-%02d', $seireki_year, $month, $day);
          $this->date_time = new DateTime($seireki, $timezone);
          break;
        }
      }
    } else {
      $this->date_time = new DateTime($time, $timezone);
    }
  }

  public function format($format) {
    /*  
    @param string $format 'K':元号
                          'k':元号略称
                          'Q':元号(英語表記)
                          'q':元号略称(英語表記)
                          'X':和暦年(前ゼロ表記)
                          'x':和暦年
                          'R':曜日
                          'V':曜日略称
                          'f':元(元年表記)
    @param string $this->date_time 変換対象となる日付(西暦)‎
    
    @return string $result 変換後の日付(和暦)‎
   */

    $week = ['日','月','火','水','木','金','土'];
    $week_long = ['日曜日','月曜日','火曜日','水曜日','木曜日','金曜日','土曜日'];
    
    $format_K = '';
    $format_k = '';
    $format_Q = '';
    $format_q = '';
    $format_X = $this->date_time->format('Y');
    $format_x = $this->date_time->format('y');
    $format_R = $week_long[$this->date_time->format('w')];
    $format_V = $week[$this->date_time->format('w')];
    $format_f = '元';
    
    foreach ($this->era_lists as $era_list) {
      $date_era = new DateTime($era_list['time']);
      if ($this->date_time->format('Ymd') >= $date_era->format('Ymd')) {
        $format_K = $era_list['jp'];
        $format_k = $era_list['jp_abbr'];
        $format_Q = $era_list['en'];
        $format_q = $era_list['en_abbr'];
        $format_x = $this->date_time->format('Y') - $date_era->format('Y') + 1;
        $format_X = sprintf('%02d', $format_x);
        break;
      }
    }

    $result = '';

    foreach (str_split($format) as $value) {
      // フォーマットが指定されていれば置換する
      if (isset(${"format_{$value}"}) && $value === 'f' && $this->year === '01'){
        $result .= ${"format_{$value}"};
      } elseif(isset(${"format_{$value}"}) && $value === 'f' && $this->year !== '01'){
        $year_value = 'X';
        $result .= ${"format_{$year_value}"};
      } elseif (isset(${"format_{$value}"}) && $value !== 'f') {
        $result .= ${"format_{$value}"};
      } else {
        $result .= $this->date_time->format($value);
      }
    }

    return $result;
  }

}

完成コードはこんな感じになっています。

それでは、パートごとに解説していきます。

コンストラクタの解説

  public function __construct($time = 'now', $timezone = null) {
    $time = preg_replace("/\\s| /", "", mb_convert_kana($time, 'n'));
    if(preg_match('/^(令|令和|r|R|平|平成|h|H|昭|昭和|s|S|大|大正|t|T|明|明治|m|M)?(元|0?[1-9])?(年|-|\\/?)?(0?[1-9]|1[0-2])?(月|-|\\/?)?(0?[1-9]|[12][0-9]|3[01])?(日?)?$/', $time, $matches)) {

      $era = $matches[1];
      $year = intval(str_replace('元', '1',$matches[2]));
      $this->year = sprintf('%02d', $year);
      $month = !empty($matches[4]) ? intval($matches[4]) : 1;
      $day = !empty($matches[6]) ? intval($matches[6]) : 1;

      foreach ($this->era_lists as $era_list) {
        if($era_list['jp'] === $era || $era_list['jp_abbr'] === $era || $era_list['en'] === $era || $era_list['en_abbr'] === $era){
          $seireki_year = $era_list['year'] + $year - 1;
          $seireki = sprintf('%04d-%02d-%02d', $seireki_year, $month, $day);
          $this->date_time = new DateTime($seireki, $timezone);
          break;
        }
      }
    } else {
      $this->date_time = new DateTime($time, $timezone);
    }
  }

コンストラクタとは、クラスからオブジェクトを作成した際に、自動的に実行される特殊なメソッドのことです。

ここでは、正規表現を使用して、入力された日付が和暦表記に対応しているかを確認し、それを変換してDateTimeオブジェクトを作成しています。

また、正規表現にマッチしない場合のエラー処理も行います。

正規表現パターンについて解説

preg_match('/^(令|令和|r|R|平|平成|h|H|昭|昭和|s|S|大|大正|t|T|明|明治|m|M)?(元|0?[1-9])?(年|-|\\/?)?(0?[1-9]|1[0-2])?(月|-|\\/?)?(0?[1-9]|[12][0-9]|3[01])?(日?)?$/', $time, $matches)

正規表現パターンを使って、preg_matchで入力された日付が和暦表記に対応しているかを確認します。

正規表現パターンは、各部分を括弧でグループ化し、$matches 配列に結果を格納しておきます。

和暦変換とDateTimeオブジェクト

$era = $matches[1];
$year = intval(str_replace('元', '1', $matches[2]));
$month = !empty($matches[4]) ? intval($matches[4]) : 1;
$day = !empty($matches[6]) ? intval($matches[6]) : 1;

foreach ($this->era_lists as $era_list) {
  // 入力された日付が各元号の期間内か判定
  $date_era = new DateTime($era_list['time']);
  if ($this->date_time->format('Ymd') >= $date_era->format('Ymd')) {
    // 和暦を西暦に変換してDateTimeオブジェクトを生成
    $seireki_year = $era_list['year'] + $year - 1;
    $seireki = sprintf('%04d-%02d-%02d', $seireki_year, $month, $day);
    $this->date_time = new DateTime($seireki, $timezone);
    break;
}

$matches 配列の結果を元に、元号や年、月、日などの情報を抽出し、各元号の期間内であれば和暦を西暦に変換して DateTime オブジェクトを生成します。

DateTime オブジェクトは $this->date_time プロパティに格納され、以降の処理で利用されます。

エラー処理

正規表現にマッチしない場合やエラーが発生した場合は、本日の日付でインスタンスが作成されるようになっています。

日付のフォーマット処理

public function format($format) {
    // 各種フォーマット指定子に基づいて和暦日付を生成
    $week = ['日', '月', '火', '水', '木', '金', '土'];
    $week_long = ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'];
    $format_K = '';
    $format_k = '';
    $format_Q = '';
    $format_q = '';
    $format_X = $this->date_time->format('Y');
    $format_x = $this->date_time->format('y');
    $format_R = $week_long[$this->date_time->format('w')];
    $format_V = $week[$this->date_time->format('w')];
    $format_f = '元';

    foreach ($this->era_lists as $era_list) {
        $date_era = new DateTime($era_list['time']);
        if ($this->date_time->format('Ymd') >= $date_era->format('Ymd')) {
            // 入力された日付が各元号の期間内であれば、各種フォーマット指定子を設定
            $format_K = $era_list['jp'];
            $format_k = $era_list['jp_abbr'];
            $format_Q = $era_list['en'];
            $format_q = $era_list['en_abbr'];
            $format_x = $this->date_time->format('Y') - $date_era->format('Y') + 1;
            $format_X = sprintf('%02d', $format_x);
            break;
        }
    }

    $result = '';

    foreach (str_split($format) as $value) {
        // フォーマットが指定されていれば置換する
        if (isset(${"format_{$value}"}) && $value === 'f' && $this->year === '01') {
            $result .= ${"format_{$value}"};
        } elseif (isset(${"format_{$value}"}) && $value === 'f' && $this->year !== '01') {
            $year_value = 'X';
            $result .= ${"format_{$year_value}"};
        } elseif (isset(${"format_{$value}"}) && $value !== 'f') {
            $result .= ${"format_{$value}"};
        } else {
            // フォーマットが指定されていない場合は、DateTimeオブジェクトのフォーマットメソッドを使用
            $result .= $this->date_time->format($value);
        }
    }

    return $result;
}

和暦のフォーマット指定子を変数で作成

$week = ['日', '月', '火', '水', '木', '金', '土'];
$week_long = ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'];
$format_K = '';
$format_k = '';
$format_Q = '';
$format_q = '';
$format_X = $this->date_time->format('Y');
$format_x = $this->date_time->format('y');
$format_R = $week_long[$this->date_time->format('w')];
$format_V = $week[$this->date_time->format('w')];
$format_f = '元';

和暦の特定の部分をフォーマットするための変数を作成します。

各元号の期間内でのフォーマット

  private $era_lists = [
    // 令和(2019年5月1日〜)
    [
      'jp' => '令和', 
      'jp_abbr' => '令',
      'en' => 'r',
      'en_abbr' => 'R',
      'time' => '20190501',
      'year' => '2019'
    ],
    // 平成(1989年1月8日〜)
    [
      'jp' => '平成',
      'jp_abbr' => '平',
      'en' => 'h',
      'en_abbr' => 'H',
      'time' => '19890108',
      'year' => '1989'
    ],
    // 昭和(1926年12月25日〜)
    [
      'jp' => '昭和',
      'jp_abbr' => '昭',
      'en' => 's',
      'en_abbr' => 'S',
      'time' => '19261225',
      'year' => '1926'
    ],
    // 大正(1912年7月30日〜)
    [
      'jp' => '大正',
      'jp_abbr' => '大',
      'en' => 't',
      'en_abbr' => 'T',
      'time' => '19120730',
      'year' => '1912'
    ],
    // 明治(1873年1月1日〜)
    // ※明治5年以前は旧暦を使用していたため、明治6年以降から対応
    [
      'jp' => '明治',
      'jp_abbr' => '明',
      'en' => 'm',
      'en_abbr' => 'M',
      'time' => '18730101',
      'year' => '1873'
    ],
  ];

完成コード冒頭で作成している$this->era_lists

foreach ($this->era_lists as $era_list) {
  $date_era = new DateTime($era_list['time']);
    if ($this->date_time->format('Ymd') >= $date_era->format('Ymd')) {
      // 入力された日付が各元号の期間内であれば、各種フォーマット指定子を設定
      $format_K = $era_list['jp'];
          $format_k = $era_list['jp_abbr'];
          $format_Q = $era_list['en'];
          $format_q = $era_list['en_abbr'];
          $format_x = $this->date_time->format('Y') - $date_era->format('Y') + 1;
          $format_X = sprintf('%02d', $format_x);
          break;
}

この$this->era_lists配列をループして、入力された日付が各元号の期間内であれば、対応する和暦の情報を取得して来ます。

指定されたフォーマットに基づいて結果を生成

foreach (str_split($format) as $value) {
  // フォーマットが指定されていれば置換する
  if (isset(${"format_{$value}"}) && $value === 'f' && $this->year === '01') {
    $result .= ${"format_{$value}"};
  } elseif (isset(${"format_{$value}"}) && $value === 'f' && $this->year !== '01') {
    $year_value = 'X';
    $result .= ${"format_{$year_value}"};
  } elseif (isset(${"format_{$value}"}) && $value !== 'f') {
    $result .= ${"format_{$value}"};
  } else {
 // フォーマットが指定されていない場合は、DateTimeオブジェクトのフォーマットメソッドを使用
    $result .= $this->date_time->format($value);
   }
}

各指定子に対応する和暦の情報が取得できた場合は、指定されたフォーマットに基づいて結果を生成します。

また、フォーマットが指定されていない場合は、 DateTime オブジェクトのフォーマットメソッドを使用して結果を出力していきます。

まとめ

今回は作成したFormatDateオブジェクトについて、コードの解説を行いました。

オブジェクトを作成したことで、自分のプロジェクトでも使いやすくなったし、日付の扱いについても詳しく学ぶことができたので、一石二鳥でした。

このオブジェクトが誰かの役に立てれば幸いです。

FormatDate作成の経緯などを知りたい方は下記の記事を読んでみてください。

また、GitHubにもコードを載せていますので、よかったらみてみてください。

タイトルとURLをコピーしました