1*db926724SAndreas Gohr<?php 2*db926724SAndreas Gohr namespace PHP81_BC; 3*db926724SAndreas Gohr 4*db926724SAndreas Gohr use DateTime; 5*db926724SAndreas Gohr use DateTimeInterface; 6*db926724SAndreas Gohr use DateTimeZone; 7*db926724SAndreas Gohr use Exception; 8*db926724SAndreas Gohr use InvalidArgumentException; 9*db926724SAndreas Gohr 10*db926724SAndreas Gohr /** 11*db926724SAndreas Gohr * Locale-formatted strftime using IntlDateFormatter (PHP 8.1 compatible) 12*db926724SAndreas Gohr * This provides a cross-platform alternative to strftime() for when it will be removed from PHP. 13*db926724SAndreas Gohr * Note that output can be slightly different between libc sprintf and this function as it is using ICU. 14*db926724SAndreas Gohr * 15*db926724SAndreas Gohr * Usage: 16*db926724SAndreas Gohr * use function \PHP81_BC\strftime; 17*db926724SAndreas Gohr * echo strftime('%A %e %B %Y %X', new \DateTime('2021-09-28 00:00:00'), 'fr_FR'); 18*db926724SAndreas Gohr * 19*db926724SAndreas Gohr * Original use: 20*db926724SAndreas Gohr * \setlocale(LC_TIME, 'fr_FR.UTF-8'); 21*db926724SAndreas Gohr * echo \strftime('%A %e %B %Y %X', strtotime('2021-09-28 00:00:00')); 22*db926724SAndreas Gohr * 23*db926724SAndreas Gohr * @param string $format Date format 24*db926724SAndreas Gohr * @param integer|string|DateTime $timestamp Timestamp 25*db926724SAndreas Gohr * @return string 26*db926724SAndreas Gohr * @author BohwaZ <https://bohwaz.net/> 27*db926724SAndreas Gohr */ 28*db926724SAndreas Gohr function strftime (string $format, $timestamp = null, ?string $locale = null) : string { 29*db926724SAndreas Gohr if (!($timestamp instanceof DateTimeInterface)) { 30*db926724SAndreas Gohr $timestamp = is_int($timestamp) ? '@' . $timestamp : (string) $timestamp; 31*db926724SAndreas Gohr 32*db926724SAndreas Gohr try { 33*db926724SAndreas Gohr $timestamp = new DateTime($timestamp); 34*db926724SAndreas Gohr } catch (Exception $e) { 35*db926724SAndreas Gohr throw new InvalidArgumentException('$timestamp argument is neither a valid UNIX timestamp, a valid date-time string or a DateTime object.', 0, $e); 36*db926724SAndreas Gohr } 37*db926724SAndreas Gohr } 38*db926724SAndreas Gohr 39*db926724SAndreas Gohr $timestamp->setTimezone(new DateTimeZone(date_default_timezone_get())); 40*db926724SAndreas Gohr 41*db926724SAndreas Gohr if (class_exists('\\IntlDateFormatter') && !isset($_SERVER['STRFTIME_NO_INTL'])) { 42*db926724SAndreas Gohr $locale = \Locale::canonicalize($locale ?? setlocale(LC_TIME, '0')); 43*db926724SAndreas Gohr $locale_formatter = new \PHP81_BC\strftime\IntlLocaleFormatter($locale); 44*db926724SAndreas Gohr } else { 45*db926724SAndreas Gohr $locale_formatter = new \PHP81_BC\strftime\DateLocaleFormatter($locale); 46*db926724SAndreas Gohr } 47*db926724SAndreas Gohr 48*db926724SAndreas Gohr // Same order as https://www.php.net/manual/en/function.strftime.php 49*db926724SAndreas Gohr $translation_table = [ 50*db926724SAndreas Gohr // Day 51*db926724SAndreas Gohr '%a' => $locale_formatter, 52*db926724SAndreas Gohr '%A' => $locale_formatter, 53*db926724SAndreas Gohr '%d' => 'd', 54*db926724SAndreas Gohr '%e' => function ($timestamp) { 55*db926724SAndreas Gohr return sprintf('% 2u', $timestamp->format('j')); 56*db926724SAndreas Gohr }, 57*db926724SAndreas Gohr '%j' => function ($timestamp) { 58*db926724SAndreas Gohr // Day number in year, 001 to 366 59*db926724SAndreas Gohr return sprintf('%03d', $timestamp->format('z')+1); 60*db926724SAndreas Gohr }, 61*db926724SAndreas Gohr '%u' => 'N', 62*db926724SAndreas Gohr '%w' => 'w', 63*db926724SAndreas Gohr 64*db926724SAndreas Gohr // Week 65*db926724SAndreas Gohr '%U' => function ($timestamp) { 66*db926724SAndreas Gohr // Number of weeks between date and first Sunday of year 67*db926724SAndreas Gohr $day = new DateTime(sprintf('%d-01 Sunday', $timestamp->format('Y'))); 68*db926724SAndreas Gohr return sprintf('%02u', 1 + ($timestamp->format('z') - $day->format('z')) / 7); 69*db926724SAndreas Gohr }, 70*db926724SAndreas Gohr '%V' => 'W', 71*db926724SAndreas Gohr '%W' => function ($timestamp) { 72*db926724SAndreas Gohr // Number of weeks between date and first Monday of year 73*db926724SAndreas Gohr $day = new DateTime(sprintf('%d-01 Monday', $timestamp->format('Y'))); 74*db926724SAndreas Gohr return sprintf('%02u', 1 + ($timestamp->format('z') - $day->format('z')) / 7); 75*db926724SAndreas Gohr }, 76*db926724SAndreas Gohr 77*db926724SAndreas Gohr // Month 78*db926724SAndreas Gohr '%b' => $locale_formatter, 79*db926724SAndreas Gohr '%B' => $locale_formatter, 80*db926724SAndreas Gohr '%h' => $locale_formatter, 81*db926724SAndreas Gohr '%m' => 'm', 82*db926724SAndreas Gohr 83*db926724SAndreas Gohr // Year 84*db926724SAndreas Gohr '%C' => function ($timestamp) { 85*db926724SAndreas Gohr // Century (-1): 19 for 20th century 86*db926724SAndreas Gohr return floor($timestamp->format('Y') / 100); 87*db926724SAndreas Gohr }, 88*db926724SAndreas Gohr '%g' => function ($timestamp) { 89*db926724SAndreas Gohr return substr($timestamp->format('o'), -2); 90*db926724SAndreas Gohr }, 91*db926724SAndreas Gohr '%G' => 'o', 92*db926724SAndreas Gohr '%y' => 'y', 93*db926724SAndreas Gohr '%Y' => 'Y', 94*db926724SAndreas Gohr 95*db926724SAndreas Gohr // Time 96*db926724SAndreas Gohr '%H' => 'H', 97*db926724SAndreas Gohr '%k' => function ($timestamp) { 98*db926724SAndreas Gohr return sprintf('% 2u', $timestamp->format('G')); 99*db926724SAndreas Gohr }, 100*db926724SAndreas Gohr '%I' => 'h', 101*db926724SAndreas Gohr '%l' => function ($timestamp) { 102*db926724SAndreas Gohr return sprintf('% 2u', $timestamp->format('g')); 103*db926724SAndreas Gohr }, 104*db926724SAndreas Gohr '%M' => 'i', 105*db926724SAndreas Gohr '%p' => 'A', // AM PM (this is reversed on purpose!) 106*db926724SAndreas Gohr '%P' => 'a', // am pm 107*db926724SAndreas Gohr '%r' => 'h:i:s A', // %I:%M:%S %p 108*db926724SAndreas Gohr '%R' => 'H:i', // %H:%M 109*db926724SAndreas Gohr '%S' => 's', 110*db926724SAndreas Gohr '%T' => 'H:i:s', // %H:%M:%S 111*db926724SAndreas Gohr '%X' => $locale_formatter, // Preferred time representation based on locale, without the date 112*db926724SAndreas Gohr 113*db926724SAndreas Gohr // Timezone 114*db926724SAndreas Gohr '%z' => 'O', 115*db926724SAndreas Gohr '%Z' => 'T', 116*db926724SAndreas Gohr 117*db926724SAndreas Gohr // Time and Date Stamps 118*db926724SAndreas Gohr '%c' => $locale_formatter, 119*db926724SAndreas Gohr '%D' => 'm/d/Y', 120*db926724SAndreas Gohr '%F' => 'Y-m-d', 121*db926724SAndreas Gohr '%s' => 'U', 122*db926724SAndreas Gohr '%x' => $locale_formatter, 123*db926724SAndreas Gohr ]; 124*db926724SAndreas Gohr 125*db926724SAndreas Gohr $out = preg_replace_callback('/(?<!%)%([_#-]?)([a-zA-Z])/', function ($match) use ($translation_table, $timestamp) { 126*db926724SAndreas Gohr $prefix = $match[1]; 127*db926724SAndreas Gohr $char = $match[2]; 128*db926724SAndreas Gohr $pattern = '%'.$char; 129*db926724SAndreas Gohr if ($pattern == '%n') { 130*db926724SAndreas Gohr return "\n"; 131*db926724SAndreas Gohr } elseif ($pattern == '%t') { 132*db926724SAndreas Gohr return "\t"; 133*db926724SAndreas Gohr } 134*db926724SAndreas Gohr 135*db926724SAndreas Gohr if (!isset($translation_table[$pattern])) { 136*db926724SAndreas Gohr throw new InvalidArgumentException(sprintf('Format "%s" is unknown in time format', $pattern)); 137*db926724SAndreas Gohr } 138*db926724SAndreas Gohr 139*db926724SAndreas Gohr $replace = $translation_table[$pattern]; 140*db926724SAndreas Gohr 141*db926724SAndreas Gohr if (is_string($replace)) { 142*db926724SAndreas Gohr $result = $timestamp->format($replace); 143*db926724SAndreas Gohr } else { 144*db926724SAndreas Gohr $result = $replace($timestamp, $pattern); 145*db926724SAndreas Gohr } 146*db926724SAndreas Gohr 147*db926724SAndreas Gohr switch ($prefix) { 148*db926724SAndreas Gohr case '_': 149*db926724SAndreas Gohr // replace leading zeros with spaces but keep last char if also zero 150*db926724SAndreas Gohr return preg_replace('/\G0(?=.)/', ' ', $result); 151*db926724SAndreas Gohr case '#': 152*db926724SAndreas Gohr case '-': 153*db926724SAndreas Gohr // remove leading zeros but keep last char if also zero 154*db926724SAndreas Gohr return preg_replace('/^0+(?=.)/', '', $result); 155*db926724SAndreas Gohr } 156*db926724SAndreas Gohr 157*db926724SAndreas Gohr return $result; 158*db926724SAndreas Gohr }, $format); 159*db926724SAndreas Gohr 160*db926724SAndreas Gohr $out = str_replace('%%', '%', $out); 161*db926724SAndreas Gohr return $out; 162*db926724SAndreas Gohr } 163