1<?php 2 3namespace PHP81_BC\strftime; 4 5use DateTimeInterface; 6use IntlDateFormatter; 7use IntlGregorianCalendar; 8 9/** 10 * This formatter uses the IntlDateFormatter class to do proper, locale aware formatting 11 */ 12class IntlLocaleFormatter extends AbstractLocaleFormatter 13{ 14 15 /** @var string[] strftime to ICU placeholders */ 16 protected $formats = [ 17 '%a' => 'EEE', // An abbreviated textual representation of the day Sun through Sat 18 '%A' => 'EEEE', // A full textual representation of the day Sunday through Saturday 19 '%b' => 'MMM', // Abbreviated month name, based on the locale Jan through Dec 20 '%B' => 'MMMM', // Full month name, based on the locale January through December 21 '%h' => 'MMM', // Abbreviated month name, based on the locale (an alias of %b) Jan through Dec 22 ]; 23 24 /** @inheritdoc */ 25 public function __invoke(DateTimeInterface $timestamp, string $format) 26 { 27 $tz = $timestamp->getTimezone(); 28 $date_type = IntlDateFormatter::FULL; 29 $time_type = IntlDateFormatter::FULL; 30 $pattern = ''; 31 32 switch ($format) { 33 // %c = Preferred date and time stamp based on locale 34 // Example: Tue Feb 5 00:45:10 2009 for February 5, 2009 at 12:45:10 AM 35 case '%c': 36 $date_type = IntlDateFormatter::LONG; 37 $time_type = IntlDateFormatter::SHORT; 38 break; 39 40 // %x = Preferred date representation based on locale, without the time 41 // Example: 02/05/09 for February 5, 2009 42 case '%x': 43 $date_type = IntlDateFormatter::SHORT; 44 $time_type = IntlDateFormatter::NONE; 45 break; 46 47 // Localized time format 48 case '%X': 49 $date_type = IntlDateFormatter::NONE; 50 $time_type = IntlDateFormatter::MEDIUM; 51 break; 52 53 default: 54 if (!isset($this->formats[$format])) { 55 throw new \RuntimeException("'$format' is not a supported locale placeholder"); 56 } 57 $pattern = $this->formats[$format]; 58 } 59 60 // In October 1582, the Gregorian calendar replaced the Julian in much of Europe, and 61 // the 4th October was followed by the 15th October. 62 // ICU (including IntlDateFormattter) interprets and formats dates based on this cutover. 63 // Posix (including strftime) and timelib (including DateTimeImmutable) instead use 64 // a "proleptic Gregorian calendar" - they pretend the Gregorian calendar has existed forever. 65 // This leads to the same instants in time, as expressed in Unix time, having different representations 66 // in formatted strings. 67 // To adjust for this, a custom calendar can be supplied with a cutover date arbitrarily far in the past. 68 $calendar = IntlGregorianCalendar::createInstance(); 69 // NOTE: IntlGregorianCalendar::createInstance DOES NOT return an IntlGregorianCalendar instance when 70 // using a non-Gregorian locale (e.g. fa_IR)! In that case, setGregorianChange will not exist. 71 if (method_exists($calendar, 'setGregorianChange')) $calendar->setGregorianChange(PHP_INT_MIN); 72 73 return (new IntlDateFormatter($this->locale, $date_type, $time_type, $tz, $calendar, $pattern))->format($timestamp); 74 } 75 76} 77