MediaWiki REL1_33
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 = $this->mLinked ?? true;
217
218 $bits = [];
219 $key = $this->keys[$this->mSource];
220 $keyLength = strlen( $key );
221 for ( $p = 0; $p < $keyLength; $p++ ) {
222 if ( $key[$p] != ' ' ) {
223 $bits[$key[$p]] = $matches[$p + 1];
224 }
225 }
226
227 return $this->formatDate( $bits, $matches[0], $linked );
228 }
229
237 private function formatDate( $bits, $orig, $link = true ) {
238 $format = $this->targets[$this->mTarget];
239
240 if ( !$link ) {
241 // strip piped links
242 $format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format );
243 // strip remaining links
244 $format = str_replace( [ '[[', ']]' ], '', $format );
245 }
246
247 # Construct new date
248 $text = '';
249 $fail = false;
250
251 // Pre-generate y/Y stuff because we need the year for the <span> title.
252 if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) {
253 $bits['y'] = $this->makeIsoYear( $bits['Y'] );
254 }
255 if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) {
256 $bits['Y'] = $this->makeNormalYear( $bits['y'] );
257 }
258
259 if ( !isset( $bits['m'] ) ) {
260 $m = $this->makeIsoMonth( $bits['F'] );
261 if ( !$m || $m == '00' ) {
262 $fail = true;
263 } else {
264 $bits['m'] = $m;
265 }
266 }
267
268 if ( !isset( $bits['d'] ) ) {
269 $bits['d'] = sprintf( '%02d', $bits['j'] );
270 }
271
272 $formatLength = strlen( $format );
273 for ( $p = 0; $p < $formatLength; $p++ ) {
274 $char = $format[$p];
275 switch ( $char ) {
276 case 'd': # ISO day of month
277 $text .= $bits['d'];
278 break;
279 case 'm': # ISO month
280 $text .= $bits['m'];
281 break;
282 case 'y': # ISO year
283 $text .= $bits['y'];
284 break;
285 case 'j': # ordinary day of month
286 if ( !isset( $bits['j'] ) ) {
287 $text .= intval( $bits['d'] );
288 } else {
289 $text .= $bits['j'];
290 }
291 break;
292 case 'F': # long month
293 if ( !isset( $bits['F'] ) ) {
294 $m = intval( $bits['m'] );
295 if ( $m > 12 || $m < 1 ) {
296 $fail = true;
297 } else {
298 $text .= $this->lang->getMonthName( $m );
299 }
300 } else {
301 $text .= ucfirst( $bits['F'] );
302 }
303 break;
304 case 'Y': # ordinary (optional BC) year
305 $text .= $bits['Y'];
306 break;
307 default:
308 $text .= $char;
309 }
310 }
311 if ( $fail ) {
312 // This occurs when parsing a date with day or month outside the bounds
313 // of possibilities.
314 $text = $orig;
315 }
316
317 $isoBits = [];
318 if ( isset( $bits['y'] ) ) {
319 $isoBits[] = $bits['y'];
320 }
321 $isoBits[] = $bits['m'];
322 $isoBits[] = $bits['d'];
323 $isoDate = implode( '-', $isoBits );
324
325 // Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
326 $text = Html::rawElement( 'span',
327 [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
328
329 return $text;
330 }
331
336 private function getMonthRegex() {
337 $names = [];
338 for ( $i = 1; $i <= 12; $i++ ) {
339 $names[] = $this->lang->getMonthName( $i );
340 $names[] = $this->lang->getMonthAbbreviation( $i );
341 }
342 return implode( '|', $names );
343 }
344
350 private function makeIsoMonth( $monthName ) {
351 $n = $this->xMonths[$this->lang->lc( $monthName )];
352 return sprintf( '%02d', $n );
353 }
354
360 private function makeIsoYear( $year ) {
361 # Assumes the year is in a nice format, as enforced by the regex
362 if ( substr( $year, -2 ) == 'BC' ) {
363 $num = intval( substr( $year, 0, -3 ) ) - 1;
364 # PHP bug note: sprintf( "%04d", -1 ) fails poorly
365 $text = sprintf( '-%04d', $num );
366
367 } else {
368 $text = sprintf( '%04d', $year );
369 }
370 return $text;
371 }
372
379 private function makeNormalYear( $iso ) {
380 if ( $iso[0] == '-' ) {
381 $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC';
382 } else {
383 $text = intval( $iso );
384 }
385 return $text;
386 }
387}
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
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
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:36
MediaWikiServices is the service locator for the application scope of MediaWiki.
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
Definition globals.txt:10
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:1999
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3069
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
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 the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
$cache
Definition mcc.php:33
CACHE_MEMCACHED $wgMainCacheType
Definition memcached.txt:63