|
11 | 11 | use craft\helpers\DateTimeHelper;
|
12 | 12 | use DateTime;
|
13 | 13 | use DateTimeZone;
|
| 14 | +use Exception; |
| 15 | +use IntlTimeZone; |
14 | 16 | use NumberFormatter;
|
| 17 | +use Yii; |
15 | 18 | use yii\base\InvalidArgumentException;
|
16 | 19 | use yii\base\InvalidConfigException;
|
| 20 | +use yii\helpers\FormatConverter; |
17 | 21 |
|
18 | 22 | /**
|
19 | 23 | * @inheritdoc
|
@@ -80,13 +84,64 @@ public function asDate($value, $format = null): string
|
80 | 84 | $format = $this->dateTimeFormats[$format]['date'];
|
81 | 85 | }
|
82 | 86 |
|
| 87 | + // https://github.com/craftcms/cms/issues/16953 |
| 88 | + $dateFormattedWithoutIntl = $this->formatDateWithoutIntl($value, $format); |
| 89 | + if ($dateFormattedWithoutIntl !== null) { |
| 90 | + return $dateFormattedWithoutIntl; |
| 91 | + } |
| 92 | + |
83 | 93 | if (strncmp($format, 'php:', 4) === 0) {
|
84 | 94 | return $this->_formatDateTimeValueWithPhpFormat($value, substr($format, 4), 'date');
|
85 | 95 | }
|
86 | 96 |
|
87 | 97 | return parent::asDate($value, $format);
|
88 | 98 | }
|
89 | 99 |
|
| 100 | + /** |
| 101 | + * Checks if the date should be formatted without the help of intl. |
| 102 | + * This is needed for some "old" dates, like dates from 19th century from Europe/Amsterdam timezone |
| 103 | + * |
| 104 | + * @param int|string|DateTime $value |
| 105 | + * @param string|null $format |
| 106 | + * @return string|null |
| 107 | + * @throws Exception |
| 108 | + */ |
| 109 | + private function formatDateWithoutIntl(int|string|DateTime $value, ?string $format): ?string |
| 110 | + { |
| 111 | + // see https://github.com/craftcms/cms/issues/16953 for more details on why this method was added |
| 112 | + |
| 113 | + if (!$value instanceof DateTime || !extension_loaded('intl')) { |
| 114 | + return null; |
| 115 | + } |
| 116 | + |
| 117 | + $intlTimeZone = IntlTimeZone::fromDateTimeZone($value->getTimezone()); |
| 118 | + $intlTimeZone->getOffset($value->getTimestamp(), true, $o1, $o2); |
| 119 | + $intlTimeZone->getOffset($value->getTimestamp(), false, $o3, $o4); |
| 120 | + |
| 121 | + // get PHP DateTime offset for this date |
| 122 | + $phpOffset = $value->getTimezone()->getOffset($value); |
| 123 | + // get DST offset that intl time zone would use (divided by 1000, b/c different units) |
| 124 | + $dstIntlOffset = ($intlTimeZone->getRawOffset() + $intlTimeZone->getDSTSavings()) / 1000; |
| 125 | + // get raw offset that intl time zone would use (divided by 1000, b/c different units) |
| 126 | + $rawIntlOffset = $intlTimeZone->getRawOffset() / 1000; |
| 127 | + |
| 128 | + // compare the php and intl offsets |
| 129 | + // if either are the same, we're good to proceed with the intl approach, as we used to; |
| 130 | + // but if they're both different, we should use PHP to format the date |
| 131 | + if ($phpOffset === $dstIntlOffset || $phpOffset === $rawIntlOffset) { |
| 132 | + return null; |
| 133 | + } |
| 134 | + |
| 135 | + // copied from yii\i18n\Formatter::formatDateTimeValue() |
| 136 | + if (strncmp($format, 'php:', 4) === 0) { |
| 137 | + $format = substr($format, 4); |
| 138 | + } else { |
| 139 | + $format = FormatConverter::convertDateIcuToPhp($format, 'date', Yii::$app->language); |
| 140 | + } |
| 141 | + |
| 142 | + return $value->format($format); |
| 143 | + } |
| 144 | + |
90 | 145 | /**
|
91 | 146 | * @inheritdoc
|
92 | 147 | * @param int|string|DateTime $value
|
|
0 commit comments