MediaWiki master
DateFormatter.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Parser;
25
29
43 private $regexes;
44
51 private const RULES = [
52 self::ALL => [
53 self::MD => self::MD,
54 self::DM => self::DM,
55 ],
56 self::NONE => [
57 self::ISO => self::ISO,
58 ],
59 self::MDY => [
60 self::DM => self::MD,
61 ],
62 self::DMY => [
63 self::MD => self::DM,
64 ],
65 ];
66
70 private $xMonths = [];
71
75 private $monthNames = [];
76
80 private const PREFERENCE_IDS = [
81 'default' => self::NONE,
82 'dmy' => self::DMY,
83 'mdy' => self::MDY,
84 'ymd' => self::YMD,
85 'ISO 8601' => self::ISO,
86 ];
87
89 private const TARGET_FORMATS = [
90 self::MDY => 'F j, Y',
91 self::DMY => 'j F Y',
92 self::YMD => 'Y F j',
93 self::ISO => 'y-m-d',
94 self::YDM => 'Y, j F',
95 self::DM => 'j F',
96 self::MD => 'F j',
97 ];
98
100 private const ALL = -1;
101
103 private const NONE = 0;
104
106 private const MDY = 1;
107
109 private const DMY = 2;
110
112 private const YMD = 3;
113
115 private const ISO = 4;
116
118 private const YDM = 5;
119
121 private const DM = 6;
122
124 private const MD = 7;
125
129 public function __construct( Language $lang ) {
130 $monthRegexParts = [];
131 for ( $i = 1; $i <= 12; $i++ ) {
132 $monthName = $lang->getMonthName( $i );
133 $monthAbbrev = $lang->getMonthAbbreviation( $i );
134 $this->monthNames[$i] = $monthName;
135 $monthRegexParts[] = preg_quote( $monthName, '/' );
136 $monthRegexParts[] = preg_quote( $monthAbbrev, '/' );
137 $this->xMonths[mb_strtolower( $monthName )] = $i;
138 $this->xMonths[mb_strtolower( $monthAbbrev )] = $i;
139 }
140
141 // Partial regular expressions
142 $monthNames = implode( '|', $monthRegexParts );
143 $dm = "(?<day>\d{1,2})[ _](?<monthName>{$monthNames})";
144 $md = "(?<monthName>{$monthNames})[ _](?<day>\d{1,2})";
145 $y = '(?<year>\d{1,4}([ _]BC|))';
146 $iso = '(?<isoYear>-?\d{4})-(?<isoMonth>\d{2})-(?<isoDay>\d{2})';
147
148 $this->regexes = [
149 self::DMY => "/^{$dm}(?: *, *| +){$y}$/iu",
150 self::YDM => "/^{$y}(?: *, *| +){$dm}$/iu",
151 self::MDY => "/^{$md}(?: *, *| +){$y}$/iu",
152 self::YMD => "/^{$y}(?: *, *| +){$md}$/iu",
153 self::DM => "/^{$dm}$/iu",
154 self::MD => "/^{$md}$/iu",
155 self::ISO => "/^{$iso}$/iu",
156 ];
157 }
158
168 public static function getInstance( ?Language $lang = null ) {
169 $lang ??= MediaWikiServices::getInstance()->getContentLanguage();
170 return MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
171 }
172
182 public function reformat( $preference, $text, $options = [] ) {
183 $userFormatId = self::PREFERENCE_IDS[$preference] ?? self::NONE;
184 foreach ( self::TARGET_FORMATS as $source => $_ ) {
185 if ( isset( self::RULES[$userFormatId][$source] ) ) {
186 # Specific rules
187 $target = self::RULES[$userFormatId][$source];
188 } elseif ( isset( self::RULES[self::ALL][$source] ) ) {
189 # General rules
190 $target = self::RULES[self::ALL][$source];
191 } elseif ( $userFormatId ) {
192 # User preference
193 $target = $userFormatId;
194 } else {
195 # Default
196 $target = $source;
197 }
198 $format = self::TARGET_FORMATS[$target];
199 $regex = $this->regexes[$source];
200
201 $text = preg_replace_callback( $regex,
202 function ( $match ) use ( $format ) {
203 $text = '';
204
205 // Pre-generate y/Y stuff because we need the year for the <span> title.
206 if ( !isset( $match['isoYear'] ) && isset( $match['year'] ) ) {
207 $match['isoYear'] = $this->makeIsoYear( $match['year'] );
208 }
209 if ( !isset( $match['year'] ) && isset( $match['isoYear'] ) ) {
210 $match['year'] = $this->makeNormalYear( $match['isoYear'] );
211 }
212
213 if ( !isset( $match['isoMonth'] ) ) {
214 $m = $this->makeIsoMonth( $match['monthName'] );
215 if ( $m === null ) {
216 // Fail
217 return $match[0];
218 }
219 $match['isoMonth'] = $m;
220 }
221
222 if ( !isset( $match['isoDay'] ) ) {
223 $match['isoDay'] = sprintf( '%02d', $match['day'] );
224 }
225
226 $formatLength = strlen( $format );
227 for ( $p = 0; $p < $formatLength; $p++ ) {
228 $char = $format[$p];
229 switch ( $char ) {
230 case 'd': // ISO day of month
231 $text .= $match['isoDay'];
232 break;
233 case 'm': // ISO month
234 $text .= $match['isoMonth'];
235 break;
236 case 'y': // ISO year
237 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
238 $text .= $match['isoYear'];
239 break;
240 case 'j': // ordinary day of month
241 if ( !isset( $match['day'] ) ) {
242 $text .= intval( $match['isoDay'] );
243 } else {
244 $text .= $match['day'];
245 }
246 break;
247 case 'F': // long month
248 $m = intval( $match['isoMonth'] );
249 if ( $m > 12 || $m < 1 ) {
250 // Fail
251 return $match[0];
252 }
253 $text .= $this->monthNames[$m];
254 break;
255 case 'Y': // ordinary (optional BC) year
256 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
257 $text .= $match['year'];
258 break;
259 default:
260 $text .= $char;
261 }
262 }
263
264 $isoBits = [];
265 if ( isset( $match['isoYear'] ) ) {
266 $isoBits[] = $match['isoYear'];
267 }
268 $isoBits[] = $match['isoMonth'];
269 $isoBits[] = $match['isoDay'];
270 $isoDate = implode( '-', $isoBits );
271
272 // Output is not strictly HTML (it's wikitext), but <span> is allowed.
273 return Html::rawElement( 'span',
274 [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
275 }, $text
276 );
277 }
278 return $text;
279 }
280
285 private function makeIsoMonth( $monthName ) {
286 $number = $this->xMonths[mb_strtolower( $monthName )] ?? null;
287 return $number !== null ? sprintf( '%02d', $number ) : null;
288 }
289
295 private function makeIsoYear( $year ) {
296 // Assumes the year is in a nice format, as enforced by the regex
297 if ( substr( $year, -2 ) == 'BC' ) {
298 $num = intval( substr( $year, 0, -3 ) ) - 1;
299 // PHP bug note: sprintf( "%04d", -1 ) fails poorly
300 $text = sprintf( '-%04d', $num );
301 } else {
302 $text = sprintf( '%04d', $year );
303 }
304 return $text;
305 }
306
313 private function makeNormalYear( $iso ) {
314 if ( $iso <= 0 ) {
315 $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC';
316 } else {
317 $text = intval( $iso );
318 }
319 return $text;
320 }
321}
322
324class_alias( DateFormatter::class, 'DateFormatter' );
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
Base class for language-specific code.
Definition Language.php:81
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
static getInstance(?Language $lang=null)
Get a DateFormatter object.
reformat( $preference, $text, $options=[])
$source