MediaWiki REL1_34
DateFormatter.php
Go to the documentation of this file.
1<?php
25
39 private $regexes;
40
47 private $rules = [];
48
52 private $xMonths = [];
53
57 private $monthNames = [];
58
63
66
68 const ALL = -1;
69
71 const NONE = 0;
72
74 const MDY = 1;
75
77 const DMY = 2;
78
80 const YMD = 3;
81
83 const ISO = 4;
84
86 const LASTPREF = 4;
87
89 const YDM = 5;
90
92 const DM = 6;
93
95 const MD = 7;
96
98 const LAST = 7;
99
103 public function __construct( Language $lang ) {
104 $monthRegexParts = [];
105 for ( $i = 1; $i <= 12; $i++ ) {
106 $monthName = $lang->getMonthName( $i );
107 $monthAbbrev = $lang->getMonthAbbreviation( $i );
108 $this->monthNames[$i] = $monthName;
109 $monthRegexParts[] = preg_quote( $monthName, '/' );
110 $monthRegexParts[] = preg_quote( $monthAbbrev, '/' );
111 $this->xMonths[mb_strtolower( $monthName )] = $i;
112 $this->xMonths[mb_strtolower( $monthAbbrev )] = $i;
113 }
114
115 // Partial regular expressions
116 $monthNames = implode( '|', $monthRegexParts );
117 $dm = "(?<day>\d{1,2})[ _](?<monthName>{$monthNames})";
118 $md = "(?<monthName>{$monthNames})[ _](?<day>\d{1,2})";
119 $y = '(?<year>\d{1,4}([ _]BC|))';
120 $iso = '(?<isoYear>-?\d{4})-(?<isoMonth>\d{2})-(?<isoDay>\d{2})';
121
122 $this->regexes = [
123 self::DMY => "/^{$dm}(?: *, *| +){$y}$/iu",
124 self::YDM => "/^{$y}(?: *, *| +){$dm}$/iu",
125 self::MDY => "/^{$md}(?: *, *| +){$y}$/iu",
126 self::YMD => "/^{$y}(?: *, *| +){$md}$/iu",
127 self::DM => "/^{$dm}$/iu",
128 self::MD => "/^{$md}$/iu",
129 self::ISO => "/^{$iso}$/iu",
130 ];
131
132 // Target date formats
133 $this->targetFormats = [
134 self::DMY => 'j F Y',
135 self::YDM => 'Y, j F',
136 self::MDY => 'F j, Y',
137 self::YMD => 'Y F j',
138 self::DM => 'j F',
139 self::MD => 'F j',
140 self::ISO => 'y-m-d',
141 ];
142
143 // Rules
144 // pref source target
145 $this->rules[self::DMY][self::MD] = self::DM;
146 $this->rules[self::ALL][self::MD] = self::MD;
147 $this->rules[self::MDY][self::DM] = self::MD;
148 $this->rules[self::ALL][self::DM] = self::DM;
149 $this->rules[self::NONE][self::ISO] = self::ISO;
150
151 $this->preferenceIDs = [
152 'default' => self::NONE,
153 'dmy' => self::DMY,
154 'mdy' => self::MDY,
155 'ymd' => self::YMD,
156 'ISO 8601' => self::ISO,
157 ];
158 }
159
169 public static function getInstance( Language $lang = null ) {
170 $lang = $lang ?? MediaWikiServices::getInstance()->getContentLanguage();
171 return MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
172 }
173
183 public function reformat( $preference, $text, $options = [] ) {
184 if ( isset( $this->preferenceIDs[$preference] ) ) {
185 $preference = $this->preferenceIDs[$preference];
186 } else {
187 $preference = self::NONE;
188 }
189 for ( $source = 1; $source <= self::LAST; $source++ ) {
190 if ( isset( $this->rules[$preference][$source] ) ) {
191 # Specific rules
192 $target = $this->rules[$preference][$source];
193 } elseif ( isset( $this->rules[self::ALL][$source] ) ) {
194 # General rules
195 $target = $this->rules[self::ALL][$source];
196 } elseif ( $preference ) {
197 # User preference
198 $target = $preference;
199 } else {
200 # Default
201 $target = $source;
202 }
203 $regex = $this->regexes[$source];
204
205 $text = preg_replace_callback( $regex,
206 function ( $match ) use ( $target ) {
207 $format = $this->targetFormats[$target];
208
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 $text .= $match['isoYear'];
245 break;
246 case 'j': // ordinary day of month
247 if ( !isset( $match['day'] ) ) {
248 $text .= intval( $match['isoDay'] );
249 } else {
250 $text .= $match['day'];
251 }
252 break;
253 case 'F': // long month
254 $m = intval( $match['isoMonth'] );
255 if ( $m > 12 || $m < 1 ) {
256 // Fail
257 return $match[0];
258 } else {
259 $text .= $this->monthNames[$m];
260 }
261 break;
262 case 'Y': // ordinary (optional BC) year
263 $text .= $match['year'];
264 break;
265 default:
266 $text .= $char;
267 }
268 }
269
270 $isoBits = [];
271 if ( isset( $match['isoYear'] ) ) {
272 $isoBits[] = $match['isoYear'];
273 }
274 $isoBits[] = $match['isoMonth'];
275 $isoBits[] = $match['isoDay'];
276 $isoDate = implode( '-', $isoBits );
277
278 // Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
279 $text = Html::rawElement( 'span',
280 [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
281
282 return $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.
const ALL
Used as a preference ID for rules that apply regardless of preference.
__construct(Language $lang)
int[][] $rules
Array of special rules.
makeNormalYear( $iso)
Make a year from an ISO year, for instance: '400 BC' from '-0399'.
int[] $preferenceIDs
A map of descriptive preference text to internal format ID.
const LAST
The highest ID that is a valid target format.
const NONE
No preference: the date may be left in the same format as the input.
string[] $targetFormats
Format strings similar to those used by date(), indexed by ID.
makeIsoMonth( $monthName)
Makes an ISO month, e.g.
static getInstance(Language $lang=null)
Get a DateFormatter object.
string[] $regexes
Date format regexes indexed the class constants.
int[] $xMonths
Month numbers by lowercase name.
string[] $monthNames
Month names by number.
const LASTPREF
The highest ID that is a valid user preference.
makeIsoYear( $year)
Make an ISO year from a year name, for instance: '-1199' from '1200 BC'.
reformat( $preference, $text, $options=[])
Internationalisation code.
Definition Language.php:37
MediaWikiServices is the service locator for the application scope of MediaWiki.
$source
if(!isset( $args[0])) $lang