MediaWiki master
DateFormatter.php
Go to the documentation of this file.
1<?php
26
40 private $regexes;
41
48 private const RULES = [
49 self::ALL => [
50 self::MD => self::MD,
51 self::DM => self::DM,
52 ],
53 self::NONE => [
54 self::ISO => self::ISO,
55 ],
56 self::MDY => [
57 self::DM => self::MD,
58 ],
59 self::DMY => [
60 self::MD => self::DM,
61 ],
62 ];
63
67 private $xMonths = [];
68
72 private $monthNames = [];
73
77 private const PREFERENCE_IDS = [
78 'default' => self::NONE,
79 'dmy' => self::DMY,
80 'mdy' => self::MDY,
81 'ymd' => self::YMD,
82 'ISO 8601' => self::ISO,
83 ];
84
86 private const TARGET_FORMATS = [
87 self::MDY => 'F j, Y',
88 self::DMY => 'j F Y',
89 self::YMD => 'Y F j',
90 self::ISO => 'y-m-d',
91 self::YDM => 'Y, j F',
92 self::DM => 'j F',
93 self::MD => 'F j',
94 ];
95
97 private const ALL = -1;
98
100 private const NONE = 0;
101
103 private const MDY = 1;
104
106 private const DMY = 2;
107
109 private const YMD = 3;
110
112 private const ISO = 4;
113
115 private const YDM = 5;
116
118 private const DM = 6;
119
121 private const MD = 7;
122
126 public function __construct( Language $lang ) {
127 $monthRegexParts = [];
128 for ( $i = 1; $i <= 12; $i++ ) {
129 $monthName = $lang->getMonthName( $i );
130 $monthAbbrev = $lang->getMonthAbbreviation( $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;
136 }
137
138 // Partial regular expressions
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})';
144
145 $this->regexes = [
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",
153 ];
154 }
155
165 public static function getInstance( Language $lang = null ) {
166 $lang ??= MediaWikiServices::getInstance()->getContentLanguage();
167 return MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
168 }
169
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] ) ) {
183 # Specific rules
184 $target = self::RULES[$userFormatId][$source];
185 } elseif ( isset( self::RULES[self::ALL][$source] ) ) {
186 # General rules
187 $target = self::RULES[self::ALL][$source];
188 } elseif ( $userFormatId ) {
189 # User preference
190 $target = $userFormatId;
191 } else {
192 # Default
193 $target = $source;
194 }
195 $format = self::TARGET_FORMATS[$target];
196 $regex = $this->regexes[$source];
197
198 $text = preg_replace_callback( $regex,
199 function ( $match ) use ( $format ) {
200 $text = '';
201
202 // Pre-generate y/Y stuff because we need the year for the <span> title.
203 if ( !isset( $match['isoYear'] ) && isset( $match['year'] ) ) {
204 $match['isoYear'] = $this->makeIsoYear( $match['year'] );
205 }
206 if ( !isset( $match['year'] ) && isset( $match['isoYear'] ) ) {
207 $match['year'] = $this->makeNormalYear( $match['isoYear'] );
208 }
209
210 if ( !isset( $match['isoMonth'] ) ) {
211 $m = $this->makeIsoMonth( $match['monthName'] );
212 if ( $m === null ) {
213 // Fail
214 return $match[0];
215 }
216 $match['isoMonth'] = $m;
217 }
218
219 if ( !isset( $match['isoDay'] ) ) {
220 $match['isoDay'] = sprintf( '%02d', $match['day'] );
221 }
222
223 $formatLength = strlen( $format );
224 for ( $p = 0; $p < $formatLength; $p++ ) {
225 $char = $format[$p];
226 switch ( $char ) {
227 case 'd': // ISO day of month
228 $text .= $match['isoDay'];
229 break;
230 case 'm': // ISO month
231 $text .= $match['isoMonth'];
232 break;
233 case 'y': // ISO year
234 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
235 $text .= $match['isoYear'];
236 break;
237 case 'j': // ordinary day of month
238 if ( !isset( $match['day'] ) ) {
239 $text .= intval( $match['isoDay'] );
240 } else {
241 $text .= $match['day'];
242 }
243 break;
244 case 'F': // long month
245 $m = intval( $match['isoMonth'] );
246 if ( $m > 12 || $m < 1 ) {
247 // Fail
248 return $match[0];
249 }
250 $text .= $this->monthNames[$m];
251 break;
252 case 'Y': // ordinary (optional BC) year
253 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
254 $text .= $match['year'];
255 break;
256 default:
257 $text .= $char;
258 }
259 }
260
261 $isoBits = [];
262 if ( isset( $match['isoYear'] ) ) {
263 $isoBits[] = $match['isoYear'];
264 }
265 $isoBits[] = $match['isoMonth'];
266 $isoBits[] = $match['isoDay'];
267 $isoDate = implode( '-', $isoBits );
268
269 // Output is not strictly HTML (it's wikitext), but <span> is allowed.
270 return Html::rawElement( 'span',
271 [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
272 }, $text
273 );
274 }
275 return $text;
276 }
277
282 private function makeIsoMonth( $monthName ) {
283 $number = $this->xMonths[mb_strtolower( $monthName )] ?? null;
284 return $number !== null ? sprintf( '%02d', $number ) : null;
285 }
286
292 private function makeIsoYear( $year ) {
293 // Assumes the year is in a nice format, as enforced by the regex
294 if ( substr( $year, -2 ) == 'BC' ) {
295 $num = intval( substr( $year, 0, -3 ) ) - 1;
296 // PHP bug note: sprintf( "%04d", -1 ) fails poorly
297 $text = sprintf( '-%04d', $num );
298 } else {
299 $text = sprintf( '%04d', $year );
300 }
301 return $text;
302 }
303
310 private function makeNormalYear( $iso ) {
311 if ( $iso <= 0 ) {
312 $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC';
313 } else {
314 $text = intval( $iso );
315 }
316 return $text;
317 }
318}
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:63
getMonthAbbreviation( $key)
Definition Language.php:760
getMonthName( $key)
Definition Language.php:733
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
Service locator for MediaWiki core services.
$source