【短命に終わった】国民の祝日.csvをダウンロードしてパースするPHPコードの解説予定地

「よっしゃ当日中にPHPクラス書いたぞ。」
「インターフェースとか例外とかファイルの扱いイケてないからもうちょっと寝かせるか。」
「[マジかよ！](http://qiita.com/jnchito/items/b8a2ed3544c1dc36fb9d)すべて台無しだ。お前はすべてを台無しにする。」
「この様はまるでお前の人生だ。」
「色んなことを始めるが、馬鹿みたいにリリースを遅らせる。」
「誰もお前のことを愛さない。」

# とりあえずコードだけ置いておく

あんまブラッシュアップしてないので公開を躊躇してたらこれだよ。

```php:JapanNationalHoliday.php
<?php

class JapanNationalHoliday {
	const DATA_URL	= 'http://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv';

	const TIMEZONE				= 'Asia/Tokyo';
	
	const YEAR_TEXT_FORMAT		= "/^(((平成)(\d{1,2})年)（(\d{4})年）),?$/";
	
	const FROM_ENCODING			= 'SJIS-win';
	const TO_ENCODING			= 'UTF-8';

	const SKIP_ROW_KEYWORD		= '名称';

	protected static $dateTimeZone = null;

	public static function buildera ($year, $fullText = null, $eraYear = null, $era = null, $eraName = null) {
		return new class ($year, $fullText, $eraYear, $era, $eraName) {
			private $data = [];

			public function __construct ($year, $fullText = null, $eraYear = null, $era = null, $eraName = null) {
				$this->data = [
					'year'		=> $year,
					'fullText'	=> $fullText,
					'eraYear'	=> $eraYear,
					'era'		=> $era,
					'eraName'	=> $eraName,
				];
			}

			public function __call ($method, $args) {
				return $this->data[$method] ?? null;
			}
		};
	}

	public static function buildHoliday (string $time, \DateTimeZone $timezone = null) {
		$timezone = $timezone ?? (static::$dateTimeZone ?? static::$dateTimeZone = new \DateTimeZone(static::TIMEZONE));
		return new class ($time, $timezone) extends \DateTime {
			protected $eraInfo		= null;
			protected $holidayName	= '';

			public function setEraInfo ($era_info) {
				$this->eraInfo = $era_info;
				return $this;
			}

			public function getEraInfo () {
				return $this->eraInfo;
			}
			
			public function setHolidayName ($holiday_name) {
				$this->holidayName = $holiday_name;
				return $this;
			}
			
			public function getHolidayName () {
				return $holidayName;
			}
		};
	}

	public static function update ($master_file_path, $options = []) {
		$is_win = substr(strtoupper(PHP_OS), 0 ,3) === 'WIN';
		$temp_dir = $options['temp_dir'] ?? ($is_win ? getenv('TEMP') : '/var/tmp');

		$allow_url_fopen = ini_get('allow_url_fopen');
		switch (true) {
			case $allow_url_fopen === '1':
			case $allow_url_fopen === true:
				break;
			default:
				throw new \Exception('allow_url_fopenが有効になっていません。php.iniまたはhttpd.confでallow_url_fopenを有効にしてください。');
		}

		$proxy_host = $options['proxy_host'] ?: null;
		$proxy_port = $options['proxy_port'] ?: null;
		if ($proxy_host !== null) {
			if (parse_url($proxy_host, \PHP_URL_SCHEME) === 'https') {
				if (extension_loaded('openssl') === false) {
					throw new \Exception('proxy hostにhttpsが使用されていますが、openssl拡張が有効になっていません。');
				}
			}
		}

		$context_config = [
			'http'	=> [
				'ignore_errors' => true,
			],
			'ssl'	=> [
				'verify_peer'		=> false,
				'verify_peer_name'	=> false,
			],
		];

		if ($proxy_host !== null) {
			$context_config['http']['proxy'] = vsprintf(
				$proxy_host !== null ? 'tcp://%s:%s' : 'tcp://%s',
				array_filter([$proxy_host, $proxy_port])
			);
			$context_config['http']['request_fulluri'] = true;
		}

		$stream_context = stream_context_create($context_config);

		file_put_contents($master_file_path, file_get_contents(static::DATA_URL, false, $stream_context));
	}

	public static function factory ($master_file_path, $options = []) {
		$class_path = get_called_class();
		$instance = new $class_path();
		$instance->init($master_file_path, $options);
		return $instance;
	}

	public function init ($master_file_path, $options = []) {
		if (!file_exists($master_file_path)) {
			static::update($master_file_path, $options);
		}

		$holiday_csv = file_get_contents($master_file_path);

		if (static::TO_ENCODING !== static::FROM_ENCODING) {
			$holiday_csv = mb_convert_encoding($holiday_csv, static::TO_ENCODING, static::FROM_ENCODING);
		}

		$fp = fopen('php://memory', 'r+');
		fwrite($fp, $holiday_csv);
		rewind($fp);
		$current_locale = setlocale(LC_ALL, '0');
		setlocale(LC_ALL, 'ja_JP.UTF-8');

		$header = [];
		foreach (fgetcsv($fp, 0) as $col) {
			preg_match(static::YEAR_TEXT_FORMAT, $col, $mat);
			$header[]	= static::buildera($mat[5], $mat[1], $mat[4], $mat[2], $mat[3]);
			$year_map[]	= $mat[5];
		}

		$list = [];
		while (($data = fgetcsv($fp, 0)) !== FALSE) {
			if ($data[0] === '' && empty(array_filter($data))) {
				break;
			}

			if ($data[0] === static::SKIP_ROW_KEYWORD) {
				continue;
			}

			foreach (array_chunk($data, 2) as $idx => $chunk) {
				$holiday = static::buildHoliday($chunk[1])->setHolidayName($chunk[0])->setEraInfo($header[$idx]);
				$list[$year_map[$idx]][$holiday->format('Y-m-d')] = $holiday;
			}
		}

		setlocale(LC_ALL, $current_locale);
		fclose($fp);

		$this->list = $list;
	}

	public function list ($year = null) {
		return $this->list[$year ?? date('Y')] ?? null;
	}

	public function isHoliday ($datetime) {
		$timestamp = is_int($datetime) ? $datetime : strtotime($datetime);
		return isset($this->list[date('Y', $timestamp)][date('Y-m-d', $timestamp)]);
	}

	public function getHoliday ($datetime) {
		$timestamp = is_int($datetime) ? $datetime : strtotime($datetime);
		return $this->list[date('Y', $timestamp)][date('Y-m-d', $timestamp)] ?? null;
	}
}
```

```php:index.php
<?php

$master_file_path = './syukujitsu.csv';

$jnh = JapanNationalHoliday::factory($master_file_path);

var_dump($jnh->isHoliday('2017-08-11'));
var_dump($jnh->getHoliday('2017-08-11'));
```