48 private const RULES = [
54 self::ISO => self::ISO,
67 private $xMonths = [];
72 private $monthNames = [];
77 private const PREFERENCE_IDS = [
78 'default' => self::NONE,
82 'ISO 8601' => self::ISO,
86 private const TARGET_FORMATS = [
87 self::MDY =>
'F j, Y',
91 self::YDM =>
'Y, j F',
97 private const ALL = -1;
100 private const NONE = 0;
103 private const MDY = 1;
106 private const DMY = 2;
109 private const YMD = 3;
112 private const ISO = 4;
115 private const YDM = 5;
118 private const DM = 6;
121 private const MD = 7;
127 $monthRegexParts = [];
128 for ( $i = 1; $i <= 12; $i++ ) {
131 $this->monthNames[$i] = $monthName;
132 $monthRegexParts[] = preg_quote( $monthName,
'/' );
133 $monthRegexParts[] = preg_quote( $monthAbbrev,
'/' );
134 $this->xMonths[mb_strtolower( $monthName )] = $i;
135 $this->xMonths[mb_strtolower( $monthAbbrev )] = $i;
139 $monthNames = implode(
'|', $monthRegexParts );
140 $dm =
"(?<day>\d{1,2})[ _](?<monthName>{$monthNames})";
141 $md =
"(?<monthName>{$monthNames})[ _](?<day>\d{1,2})";
142 $y =
'(?<year>\d{1,4}([ _]BC|))';
143 $iso =
'(?<isoYear>-?\d{4})-(?<isoMonth>\d{2})-(?<isoDay>\d{2})';
146 self::DMY =>
"/^{$dm}(?: *, *| +){$y}$/iu",
147 self::YDM =>
"/^{$y}(?: *, *| +){$dm}$/iu",
148 self::MDY =>
"/^{$md}(?: *, *| +){$y}$/iu",
149 self::YMD =>
"/^{$y}(?: *, *| +){$md}$/iu",
150 self::DM =>
"/^{$dm}$/iu",
151 self::MD =>
"/^{$md}$/iu",
152 self::ISO =>
"/^{$iso}$/iu",
166 $lang ??= MediaWikiServices::getInstance()->getContentLanguage();
167 return MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
179 public function reformat( $preference, $text, $options = [] ) {
180 $userFormatId = self::PREFERENCE_IDS[$preference] ?? self::NONE;
181 foreach ( self::TARGET_FORMATS as
$source => $_ ) {
182 if ( isset( self::RULES[$userFormatId][
$source] ) ) {
184 $target = self::RULES[$userFormatId][
$source];
185 } elseif ( isset( self::RULES[self::ALL][
$source] ) ) {
187 $target = self::RULES[self::ALL][
$source];
188 } elseif ( $userFormatId ) {
190 $target = $userFormatId;
195 $format = self::TARGET_FORMATS[$target];
196 $regex = $this->regexes[
$source];
198 $text = preg_replace_callback( $regex,
199 function ( $match ) use ( $format ) {
203 if ( !isset( $match[
'isoYear'] ) && isset( $match[
'year'] ) ) {
204 $match[
'isoYear'] = $this->makeIsoYear( $match[
'year'] );
206 if ( !isset( $match[
'year'] ) && isset( $match[
'isoYear'] ) ) {
207 $match[
'year'] = $this->makeNormalYear( $match[
'isoYear'] );
210 if ( !isset( $match[
'isoMonth'] ) ) {
211 $m = $this->makeIsoMonth( $match[
'monthName'] );
216 $match[
'isoMonth'] = $m;
219 if ( !isset( $match[
'isoDay'] ) ) {
220 $match[
'isoDay'] = sprintf(
'%02d', $match[
'day'] );
223 $formatLength = strlen( $format );
224 for ( $p = 0; $p < $formatLength; $p++ ) {
228 $text .= $match[
'isoDay'];
231 $text .= $match[
'isoMonth'];
235 $text .= $match[
'isoYear'];
238 if ( !isset( $match[
'day'] ) ) {
239 $text .= intval( $match[
'isoDay'] );
241 $text .= $match[
'day'];
245 $m = intval( $match[
'isoMonth'] );
246 if ( $m > 12 || $m < 1 ) {
250 $text .= $this->monthNames[$m];
254 $text .= $match[
'year'];
262 if ( isset( $match[
'isoYear'] ) ) {
263 $isoBits[] = $match[
'isoYear'];
265 $isoBits[] = $match[
'isoMonth'];
266 $isoBits[] = $match[
'isoDay'];
267 $isoDate = implode(
'-', $isoBits );
270 return Html::rawElement(
'span',
271 [
'class' =>
'mw-formatted-date',
'title' => $isoDate ], $text );
282 private function makeIsoMonth( $monthName ) {
283 $number = $this->xMonths[mb_strtolower( $monthName )] ??
null;
284 return $number !==
null ? sprintf(
'%02d', $number ) : null;
292 private function makeIsoYear( $year ) {
294 if ( substr( $year, -2 ) ==
'BC' ) {
295 $num = intval( substr( $year, 0, -3 ) ) - 1;
297 $text = sprintf(
'-%04d', $num );
299 $text = sprintf(
'%04d', $year );
310 private function makeNormalYear( $iso ) {
311 if ( $iso[0] ==
'-' ) {
312 $text = ( intval( substr( $iso, 1 ) ) + 1 ) .
' BC';
314 $text = intval( $iso );
Base class for language-specific code.
getMonthAbbreviation( $key)