MediaWiki REL1_32
DateFormatter.php
Go to the documentation of this file.
1<?php
25
33 private $monthNames = '';
34
35 private $regexes;
37
38 private $lang, $mLinked;
39
41 private $keys;
42
44 private $targets;
45
46 const ALL = -1;
47 const NONE = 0;
48 const MDY = 1;
49 const DMY = 2;
50 const YMD = 3;
51 const ISO1 = 4;
52 const LASTPREF = 4;
53 const ISO2 = 5;
54 const YDM = 6;
55 const DM = 7;
56 const MD = 8;
57 const LAST = 8;
58
62 public function __construct( Language $lang ) {
63 $this->lang = $lang;
64
65 $this->monthNames = $this->getMonthRegex();
66 for ( $i = 1; $i <= 12; $i++ ) {
67 $this->xMonths[$this->lang->lc( $this->lang->getMonthName( $i ) )] = $i;
68 $this->xMonths[$this->lang->lc( $this->lang->getMonthAbbreviation( $i ) )] = $i;
69 }
70
71 $this->regexTrail = '(?![a-z])/iu';
72
73 # Partial regular expressions
74 $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')\]\]';
75 $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})\]\]';
76 $this->prxY = '\[\[(\d{1,4}([ _]BC|))\]\]';
77 $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})\]\]';
78 $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})\]\]';
79
80 # Real regular expressions
81 $this->regexes[self::DMY] = "/{$this->prxDM}(?: *, *| +){$this->prxY}{$this->regexTrail}";
82 $this->regexes[self::YDM] = "/{$this->prxY}(?: *, *| +){$this->prxDM}{$this->regexTrail}";
83 $this->regexes[self::MDY] = "/{$this->prxMD}(?: *, *| +){$this->prxY}{$this->regexTrail}";
84 $this->regexes[self::YMD] = "/{$this->prxY}(?: *, *| +){$this->prxMD}{$this->regexTrail}";
85 $this->regexes[self::DM] = "/{$this->prxDM}{$this->regexTrail}";
86 $this->regexes[self::MD] = "/{$this->prxMD}{$this->regexTrail}";
87 $this->regexes[self::ISO1] = "/{$this->prxISO1}{$this->regexTrail}";
88 $this->regexes[self::ISO2] = "/{$this->prxISO2}{$this->regexTrail}";
89
90 # Extraction keys
91 # See the comments in replace() for the meaning of the letters
92 $this->keys[self::DMY] = 'jFY';
93 $this->keys[self::YDM] = 'Y jF';
94 $this->keys[self::MDY] = 'FjY';
95 $this->keys[self::YMD] = 'Y Fj';
96 $this->keys[self::DM] = 'jF';
97 $this->keys[self::MD] = 'Fj';
98 $this->keys[self::ISO1] = 'ymd'; # y means ISO year
99 $this->keys[self::ISO2] = 'ymd';
100
101 # Target date formats
102 $this->targets[self::DMY] = '[[F j|j F]] [[Y]]';
103 $this->targets[self::YDM] = '[[Y]], [[F j|j F]]';
104 $this->targets[self::MDY] = '[[F j]], [[Y]]';
105 $this->targets[self::YMD] = '[[Y]] [[F j]]';
106 $this->targets[self::DM] = '[[F j|j F]]';
107 $this->targets[self::MD] = '[[F j]]';
108 $this->targets[self::ISO1] = '[[Y|y]]-[[F j|m-d]]';
109 $this->targets[self::ISO2] = '[[y-m-d]]';
110
111 # Rules
112 # pref source target
113 $this->rules[self::DMY][self::MD] = self::DM;
114 $this->rules[self::ALL][self::MD] = self::MD;
115 $this->rules[self::MDY][self::DM] = self::MD;
116 $this->rules[self::ALL][self::DM] = self::DM;
117 $this->rules[self::NONE][self::ISO2] = self::ISO1;
118
119 $this->preferences = [
120 'default' => self::NONE,
121 'dmy' => self::DMY,
122 'mdy' => self::MDY,
123 'ymd' => self::YMD,
124 'ISO 8601' => self::ISO1,
125 ];
126 }
127
135 public static function getInstance( Language $lang = null ) {
136 global $wgMainCacheType;
137
138 $lang = $lang ?? MediaWikiServices::getInstance()->getContentLanguage();
139 $cache = ObjectCache::getLocalServerInstance( $wgMainCacheType );
140
141 static $dateFormatter = false;
142 if ( !$dateFormatter ) {
143 $dateFormatter = $cache->getWithSetCallback(
144 $cache->makeKey( 'dateformatter', $lang->getCode() ),
145 $cache::TTL_HOUR,
146 function () use ( $lang ) {
147 return new DateFormatter( $lang );
148 }
149 );
150 }
151
152 return $dateFormatter;
153 }
154
162 public function reformat( $preference, $text, $options = [ 'linked' ] ) {
163 $linked = in_array( 'linked', $options );
164 $match_whole = in_array( 'match-whole', $options );
165
166 if ( isset( $this->preferences[$preference] ) ) {
167 $preference = $this->preferences[$preference];
168 } else {
169 $preference = self::NONE;
170 }
171 for ( $i = 1; $i <= self::LAST; $i++ ) {
172 $this->mSource = $i;
173 if ( isset( $this->rules[$preference][$i] ) ) {
174 # Specific rules
175 $this->mTarget = $this->rules[$preference][$i];
176 } elseif ( isset( $this->rules[self::ALL][$i] ) ) {
177 # General rules
178 $this->mTarget = $this->rules[self::ALL][$i];
179 } elseif ( $preference ) {
180 # User preference
181 $this->mTarget = $preference;
182 } else {
183 # Default
184 $this->mTarget = $i;
185 }
186 $regex = $this->regexes[$i];
187
188 // Horrible hack
189 if ( !$linked ) {
190 $regex = str_replace( [ '\[\[', '\]\]' ], '', $regex );
191 }
192
193 if ( $match_whole ) {
194 // Let's hope this works
195 $regex = preg_replace( '!^/!', '/^', $regex );
196 $regex = str_replace( $this->regexTrail,
197 '$' . $this->regexTrail, $regex );
198 }
199
200 // Another horrible hack
201 $this->mLinked = $linked;
202 $text = preg_replace_callback( $regex, [ $this, 'replace' ], $text );
203 unset( $this->mLinked );
204 }
205 return $text;
206 }
207
214 private function replace( $matches ) {
215 # Extract information from $matches
216 $linked = true;
217 if ( isset( $this->mLinked ) ) {
218 $linked = $this->mLinked;
219 }
220
221 $bits = [];
222 $key = $this->keys[$this->mSource];
223 $keyLength = strlen( $key );
224 for ( $p = 0; $p < $keyLength; $p++ ) {
225 if ( $key[$p] != ' ' ) {
226 $bits[$key[$p]] = $matches[$p + 1];
227 }
228 }
229
230 return $this->formatDate( $bits, $matches[0], $linked );
231 }
232
240 private function formatDate( $bits, $orig, $link = true ) {
241 $format = $this->targets[$this->mTarget];
242
243 if ( !$link ) {
244 // strip piped links
245 $format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format );
246 // strip remaining links
247 $format = str_replace( [ '[[', ']]' ], '', $format );
248 }
249
250 # Construct new date
251 $text = '';
252 $fail = false;
253
254 // Pre-generate y/Y stuff because we need the year for the <span> title.
255 if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) {
256 $bits['y'] = $this->makeIsoYear( $bits['Y'] );
257 }
258 if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) {
259 $bits['Y'] = $this->makeNormalYear( $bits['y'] );
260 }
261
262 if ( !isset( $bits['m'] ) ) {
263 $m = $this->makeIsoMonth( $bits['F'] );
264 if ( !$m || $m == '00' ) {
265 $fail = true;
266 } else {
267 $bits['m'] = $m;
268 }
269 }
270
271 if ( !isset( $bits['d'] ) ) {
272 $bits['d'] = sprintf( '%02d', $bits['j'] );
273 }
274
275 $formatLength = strlen( $format );
276 for ( $p = 0; $p < $formatLength; $p++ ) {
277 $char = $format[$p];
278 switch ( $char ) {
279 case 'd': # ISO day of month
280 $text .= $bits['d'];
281 break;
282 case 'm': # ISO month
283 $text .= $bits['m'];
284 break;
285 case 'y': # ISO year
286 $text .= $bits['y'];
287 break;
288 case 'j': # ordinary day of month
289 if ( !isset( $bits['j'] ) ) {
290 $text .= intval( $bits['d'] );
291 } else {
292 $text .= $bits['j'];
293 }
294 break;
295 case 'F': # long month
296 if ( !isset( $bits['F'] ) ) {
297 $m = intval( $bits['m'] );
298 if ( $m > 12 || $m < 1 ) {
299 $fail = true;
300 } else {
301 $text .= $this->lang->getMonthName( $m );
302 }
303 } else {
304 $text .= ucfirst( $bits['F'] );
305 }
306 break;
307 case 'Y': # ordinary (optional BC) year
308 $text .= $bits['Y'];
309 break;
310 default:
311 $text .= $char;
312 }
313 }
314 if ( $fail ) {
315 // This occurs when parsing a date with day or month outside the bounds
316 // of possibilities.
317 $text = $orig;
318 }
319
320 $isoBits = [];
321 if ( isset( $bits['y'] ) ) {
322 $isoBits[] = $bits['y'];
323 }
324 $isoBits[] = $bits['m'];
325 $isoBits[] = $bits['d'];
326 $isoDate = implode( '-', $isoBits );
327
328 // Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
329 $text = Html::rawElement( 'span',
330 [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
331
332 return $text;
333 }
334
339 private function getMonthRegex() {
340 $names = [];
341 for ( $i = 1; $i <= 12; $i++ ) {
342 $names[] = $this->lang->getMonthName( $i );
343 $names[] = $this->lang->getMonthAbbreviation( $i );
344 }
345 return implode( '|', $names );
346 }
347
353 private function makeIsoMonth( $monthName ) {
354 $n = $this->xMonths[$this->lang->lc( $monthName )];
355 return sprintf( '%02d', $n );
356 }
357
363 private function makeIsoYear( $year ) {
364 # Assumes the year is in a nice format, as enforced by the regex
365 if ( substr( $year, -2 ) == 'BC' ) {
366 $num = intval( substr( $year, 0, -3 ) ) - 1;
367 # PHP bug note: sprintf( "%04d", -1 ) fails poorly
368 $text = sprintf( '-%04d', $num );
369
370 } else {
371 $text = sprintf( '%04d', $year );
372 }
373 return $text;
374 }
375
382 private function makeNormalYear( $iso ) {
383 if ( $iso[0] == '-' ) {
384 $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC';
385 } else {
386 $text = intval( $iso );
387 }
388 return $text;
389 }
390}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control means(i) the power
Date formatter, recognises dates in plain text and formats them according to user preferences.
__construct(Language $lang)
makeNormalYear( $iso)
Make a year one from an ISO year, for instance: '400 BC' from '-0399'.
replace( $matches)
Regexp replacement callback.
reformat( $preference, $text, $options=[ 'linked'])
makeIsoMonth( $monthName)
Makes an ISO month, e.g.
static getInstance(Language $lang=null)
Get a DateFormatter object.
formatDate( $bits, $orig, $link=true)
getMonthRegex()
Return a regex that can be used to find month names in string.
string[] $targets
makeIsoYear( $year)
Make an ISO year from a year name, for instance: '-1199' from '1200 BC'.
Internationalisation code.
Definition Language.php:35
MediaWikiServices is the service locator for the application scope of MediaWiki.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2050
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3106
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback function
Definition injection.txt:30
$cache
Definition mcc.php:33
CACHE_MEMCACHED $wgMainCacheType
Definition memcached.txt:63