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