MediaWiki REL1_39
DateFormatter.php
Go to the documentation of this file.
1<?php
25
39 private $regexes;
40
47 private const RULES = [
48 self::ALL => [
49 self::MD => self::MD,
50 self::DM => self::DM,
51 ],
52 self::NONE => [
53 self::ISO => self::ISO,
54 ],
55 self::MDY => [
56 self::DM => self::MD,
57 ],
58 self::DMY => [
59 self::MD => self::DM,
60 ],
61 ];
62
66 private $xMonths = [];
67
71 private $monthNames = [];
72
76 private const PREFERENCE_IDS = [
77 'default' => self::NONE,
78 'dmy' => self::DMY,
79 'mdy' => self::MDY,
80 'ymd' => self::YMD,
81 'ISO 8601' => self::ISO,
82 ];
83
85 private const TARGET_FORMATS = [
86 self::DMY => 'j F Y',
87 self::YDM => 'Y, j F',
88 self::MDY => 'F j, Y',
89 self::YMD => 'Y F j',
90 self::DM => 'j F',
91 self::MD => 'F j',
92 self::ISO => 'y-m-d',
93 ];
94
96 private const ALL = -1;
97
99 private const NONE = 0;
100
102 private const MDY = 1;
103
105 private const DMY = 2;
106
108 private const YMD = 3;
109
111 private const ISO = 4;
112
114 private const YDM = 5;
115
117 private const DM = 6;
118
120 private const MD = 7;
121
123 private const LAST = 7;
124
128 public function __construct( Language $lang ) {
129 $monthRegexParts = [];
130 for ( $i = 1; $i <= 12; $i++ ) {
131 $monthName = $lang->getMonthName( $i );
132 $monthAbbrev = $lang->getMonthAbbreviation( $i );
133 $this->monthNames[$i] = $monthName;
134 $monthRegexParts[] = preg_quote( $monthName, '/' );
135 $monthRegexParts[] = preg_quote( $monthAbbrev, '/' );
136 $this->xMonths[mb_strtolower( $monthName )] = $i;
137 $this->xMonths[mb_strtolower( $monthAbbrev )] = $i;
138 }
139
140 // Partial regular expressions
141 $monthNames = implode( '|', $monthRegexParts );
142 $dm = "(?<day>\d{1,2})[ _](?<monthName>{$monthNames})";
143 $md = "(?<monthName>{$monthNames})[ _](?<day>\d{1,2})";
144 $y = '(?<year>\d{1,4}([ _]BC|))';
145 $iso = '(?<isoYear>-?\d{4})-(?<isoMonth>\d{2})-(?<isoDay>\d{2})';
146
147 $this->regexes = [
148 self::DMY => "/^{$dm}(?: *, *| +){$y}$/iu",
149 self::YDM => "/^{$y}(?: *, *| +){$dm}$/iu",
150 self::MDY => "/^{$md}(?: *, *| +){$y}$/iu",
151 self::YMD => "/^{$y}(?: *, *| +){$md}$/iu",
152 self::DM => "/^{$dm}$/iu",
153 self::MD => "/^{$md}$/iu",
154 self::ISO => "/^{$iso}$/iu",
155 ];
156 }
157
167 public static function getInstance( Language $lang = null ) {
168 $lang = $lang ?? MediaWikiServices::getInstance()->getContentLanguage();
169 return MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
170 }
171
181 public function reformat( $preference, $text, $options = [] ) {
182 if ( isset( self::PREFERENCE_IDS[$preference] ) ) {
183 $userFormatId = self::PREFERENCE_IDS[$preference];
184 } else {
185 $userFormatId = self::NONE;
186 }
187 for ( $source = 1; $source <= self::LAST; $source++ ) {
188 if ( isset( self::RULES[$userFormatId][$source] ) ) {
189 # Specific rules
190 // @phan-suppress-next-line PhanTypeInvalidDimOffset
191 $target = self::RULES[$userFormatId][$source];
192 } elseif ( isset( self::RULES[self::ALL][$source] ) ) {
193 # General rules
194 // @phan-suppress-next-line PhanTypeInvalidDimOffset
195 $target = self::RULES[self::ALL][$source];
196 } elseif ( $userFormatId ) {
197 # User preference
198 $target = $userFormatId;
199 } else {
200 # Default
201 $target = $source;
202 }
203 // @phan-suppress-next-line PhanTypeMismatchDimFetchNullable
204 $format = self::TARGET_FORMATS[$target];
205 $regex = $this->regexes[$source];
206
207 $text = preg_replace_callback( $regex,
208 function ( $match ) use ( $format ) {
209 $text = '';
210
211 // Pre-generate y/Y stuff because we need the year for the <span> title.
212 if ( !isset( $match['isoYear'] ) && isset( $match['year'] ) ) {
213 $match['isoYear'] = $this->makeIsoYear( $match['year'] );
214 }
215 if ( !isset( $match['year'] ) && isset( $match['isoYear'] ) ) {
216 $match['year'] = $this->makeNormalYear( $match['isoYear'] );
217 }
218
219 if ( !isset( $match['isoMonth'] ) ) {
220 $m = $this->makeIsoMonth( $match['monthName'] );
221 if ( $m === false ) {
222 // Fail
223 return $match[0];
224 } else {
225 $match['isoMonth'] = $m;
226 }
227 }
228
229 if ( !isset( $match['isoDay'] ) ) {
230 $match['isoDay'] = sprintf( '%02d', $match['day'] );
231 }
232
233 $formatLength = strlen( $format );
234 for ( $p = 0; $p < $formatLength; $p++ ) {
235 $char = $format[$p];
236 switch ( $char ) {
237 case 'd': // ISO day of month
238 $text .= $match['isoDay'];
239 break;
240 case 'm': // ISO month
241 $text .= $match['isoMonth'];
242 break;
243 case 'y': // ISO year
244 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
245 $text .= $match['isoYear'];
246 break;
247 case 'j': // ordinary day of month
248 if ( !isset( $match['day'] ) ) {
249 $text .= intval( $match['isoDay'] );
250 } else {
251 $text .= $match['day'];
252 }
253 break;
254 case 'F': // long month
255 $m = intval( $match['isoMonth'] );
256 if ( $m > 12 || $m < 1 ) {
257 // Fail
258 return $match[0];
259 } else {
260 $text .= $this->monthNames[$m];
261 }
262 break;
263 case 'Y': // ordinary (optional BC) year
264 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
265 $text .= $match['year'];
266 break;
267 default:
268 $text .= $char;
269 }
270 }
271
272 $isoBits = [];
273 if ( isset( $match['isoYear'] ) ) {
274 $isoBits[] = $match['isoYear'];
275 }
276 $isoBits[] = $match['isoMonth'];
277 $isoBits[] = $match['isoDay'];
278 $isoDate = implode( '-', $isoBits );
279
280 // Output is not strictly HTML (it's wikitext), but <span> is allowed.
281 return Html::rawElement( 'span',
282 [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
283 }, $text
284 );
285 }
286 return $text;
287 }
288
294 private function makeIsoMonth( $monthName ) {
295 $isoMonth = $this->xMonths[mb_strtolower( $monthName )] ?? false;
296 if ( $isoMonth === false ) {
297 return false;
298 }
299 return sprintf( '%02d', $isoMonth );
300 }
301
307 private function makeIsoYear( $year ) {
308 // Assumes the year is in a nice format, as enforced by the regex
309 if ( substr( $year, -2 ) == 'BC' ) {
310 $num = intval( substr( $year, 0, -3 ) ) - 1;
311 // PHP bug note: sprintf( "%04d", -1 ) fails poorly
312 $text = sprintf( '-%04d', $num );
313 } else {
314 $text = sprintf( '%04d', $year );
315 }
316 return $text;
317 }
318
325 private function makeNormalYear( $iso ) {
326 if ( $iso[0] == '-' ) {
327 $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC';
328 } else {
329 $text = intval( $iso );
330 }
331 return $text;
332 }
333}
Date formatter.
__construct(Language $lang)
static getInstance(Language $lang=null)
Get a DateFormatter object.
reformat( $preference, $text, $options=[])
Base class for language-specific code.
Definition Language.php:53
Service locator for MediaWiki core services.
$source
if(!isset( $args[0])) $lang