site_bc\strftime

composer にあった site_bc\strftime がイマイチだったのでかなり前に自作したんだけど、公開してなかったので供養。

最近だと生成ツールに「strftime() と互換性のある関数作って」と言えば作ってくれそうだけど。
(ちょっと前に試したらイマイチだったけど、近年ならマシになってるかも)

<?php
namespace site_bc;

use Exception;

function strftime($format, $ut=null) {
	if (STRFTIME::$FUNC) {
		return STRFTIME::conv_func($format, $ut);
	}

	$date_format = STRFTIME::conv_format($format);
	return date($date_format, $ut);
}

class STRFTIME {
	static $FUNC = true;
	static $FULL = true;
	const FORMAT = [
		'a' => 'D',
		'b' => 'M',
		'c' => ['D M j H:i:s Y', 'conv_c1'],
		'd' => 'd',
		'e' => ['j', 'conv_e1'],
		'f' => '\f',
		'g' => ['o', 'conv_g1'],
		'h' => 'M',
		'i' => '\i',
		'j' => ['z', 'conv_j1'],
		'k' => ['G', 'conv_k1'],
		'l' => ['g', 'conv_l1'],
		'm' => 'm',
		'n' => "\n",
		'o' => '\o',
		'p' => 'A',
		'q' => '\q',
		'r' => 'h:i:s A',
		's' => 'U',
		't' => "\t",
		'u' => 'N',
		'v' => ['j-M-Y', 'conv_v1'],
		'w' => 'w',
		'x' => 'm/d/y',
		'y' => 'y',
		'z' => 'O',

		'A' => 'l',
		'B' => 'F',
		'C' => ['', 'conv_c2'],
		'D' => 'm/d/y',
		'E' => '',		# 特殊ケースあり
		'F' => 'Y-m-d',
		'G' => 'o',
		'H' => 'H',
		'I' => 'h',
		'J' => '\J',
		'K' => '\K',
		'L' => '\L',
		'M' => 'i',
		'N' => '\N',
		'O' => '',		# 特殊ケースあり
		'P' => '\P',
		'Q' => '\Q',
		'R' => 'H:i',
		'S' => 's',
		'T' => 'H:i:s',
		'U' => ['W', 'conv_u2'],
		'V' => 'W',
		'W' => ['W', 'conv_w2'],
		'X' => 'H:i:s',
		'Y' => 'Y',
		'Z' => 'T',

		'%' => '%',
	];

	static $search;
	static $replace;
	static $invalid;
	static function _mk_replace() {
		if (! isset(static::$search)) {
			static::$search = [];
			static::$replace = [];
			static::$invalid = '';

			foreach (static::FORMAT as $f => $t) {
				static::$search[] = "%$f";
				if (is_array($t)) {
					static::$replace[] = $t[0];
					static::$invalid .= $f;
				}
				else {
					static::$replace[] = $t;
				}
			}
		}

		return [ static::$search, static::$replace, static::$invalid ];
	}
	static function conv_format($format, $full=null) {
		if ($full === null) $full = static::$FULL;

		$format = preg_replace('/(?<![%])([a-zA-Z])/', '\\\\$1', $format);;

		$sr = static::_mk_replace();
		if ($full) {
			if (preg_match("/(%[{$sr[2]}])/", $format, $m)) {
				throw new Exception("can't convert date format '{$m[1]}'");
			}
		}

		return str_replace($sr[0], $sr[1], $format);
	}

	static function conv_func($format, $ut=null) {
		$ut ??= time();

		$date_str = preg_replace_callback(
			'/[%]([a-zA-Z%])/',
			function ($m) use ($ut) {
				$conv = static::FORMAT[$m[1]];

				if (is_array($conv)) {
					return static::{$conv[1]}($ut);
				}

				return date($conv, $ut);
			},
			$format
		);

		return $date_str;
	}

	static function conv_e1($ut) {
		return str_pad(idate('d', $ut), 2, ' ', STR_PAD_LEFT);
	}
	static function conv_g1($ut) {
		return substr( date('o', $ut), -2);
	}
	static function conv_c2($ut) {
		return (int)(idate('Y', $ut) / 100);
	}
	static function conv_k1($ut) {
		return str_pad(idate('H', $ut), 2, ' ', STR_PAD_LEFT);
	}
	static function conv_l1($ut) {
		return str_pad(idate('h', $ut), 2, ' ', STR_PAD_LEFT);
	}
	static function conv_j1($ut) {
		return str_pad((date('z', $ut) + 1), 3, '0', STR_PAD_LEFT);
	}
	static function conv_c1($ut) {
		$day = static::conv_e1($ut);
		return date("D M $day H:i:s Y", $ut);
	}
	static function conv_v1($ut) {
		$day = static::conv_e1($ut);
		return date("$day-M-Y", $ut);
	}

	static function calc_weeknum($ut, $target) {
		$year = idate('Y', $ut);
		$ut_d1 = mktime(0, 0, 0, 1, 1, $year);
		$d1_week = idate('w', $ut_d1);
		$next = (7 - $d1_week + $target) % 7;
		$ut_ds = mktime(0, 0, 0, 1, $next + 1, $year);
		$dd = date('z', $ut) - date('z', $ut_ds);
		$weeknum = floor($dd / 7) + 1;
		return str_pad($weeknum, 2, '0', STR_PAD_LEFT);
	}
	static function conv_u2($ut) {
		return static::calc_weeknum($ut, 0);
	}
	static function conv_w2($ut) {
		return static::calc_weeknum($ut, 1);
	}

}