MediaWiki  1.33.0
DateFormatter.php
Go to the documentation of this file.
1 <?php
25 
32  private $mSource, $mTarget;
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();
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 }
DateFormatter\$regexes
$regexes
Definition: DateFormatter.php:35
DateFormatter\formatDate
formatDate( $bits, $orig, $link=true)
Definition: DateFormatter.php:237
DateFormatter\replace
replace( $matches)
Regexp replacement callback.
Definition: DateFormatter.php:214
DateFormatter\$mLinked
$mLinked
Definition: DateFormatter.php:38
DateFormatter\reformat
reformat( $preference, $text, $options=[ 'linked'])
Definition: DateFormatter.php:162
DateFormatter\LASTPREF
const LASTPREF
Definition: DateFormatter.php:52
DateFormatter\$xMonths
$xMonths
Definition: DateFormatter.php:36
DateFormatter\$targets
string[] $targets
Definition: DateFormatter.php:44
php
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:35
means
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
DateFormatter\makeIsoMonth
makeIsoMonth( $monthName)
Makes an ISO month, e.g.
Definition: DateFormatter.php:350
DateFormatter\YMD
const YMD
Definition: DateFormatter.php:50
DateFormatter\$lang
$lang
Definition: DateFormatter.php:38
$matches
$matches
Definition: NoLocalSettings.php:24
DateFormatter\$monthNames
$monthNames
Definition: DateFormatter.php:33
DateFormatter\NONE
const NONE
Definition: DateFormatter.php:47
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
DateFormatter\MD
const MD
Definition: DateFormatter.php:56
DateFormatter\$keys
string[] $keys
Definition: DateFormatter.php:41
DateFormatter\$preferences
$preferences
Definition: DateFormatter.php:36
DateFormatter\$rules
$rules
Definition: DateFormatter.php:36
DateFormatter\DM
const DM
Definition: DateFormatter.php:55
DateFormatter\__construct
__construct(Language $lang)
Definition: DateFormatter.php:62
DateFormatter\DMY
const DMY
Definition: DateFormatter.php:49
DateFormatter\MDY
const MDY
Definition: DateFormatter.php:48
DateFormatter\YDM
const YDM
Definition: DateFormatter.php:54
DateFormatter\getMonthRegex
getMonthRegex()
Return a regex that can be used to find month names in string.
Definition: DateFormatter.php:336
$cache
$cache
Definition: mcc.php:33
$wgMainCacheType
CACHE_MEMCACHED $wgMainCacheType
Definition: memcached.txt:63
$options
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:1985
DateFormatter\makeIsoYear
makeIsoYear( $year)
Make an ISO year from a year name, for instance: '-1199' from '1200 BC'.
Definition: DateFormatter.php:360
DateFormatter\$mSource
$mSource
Definition: DateFormatter.php:32
DateFormatter\ISO1
const ISO1
Definition: DateFormatter.php:51
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3053
of
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
Definition: globals.txt:10
DateFormatter\LAST
const LAST
Definition: DateFormatter.php:57
MediaWikiServices
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 MediaWikiServices
Definition: injection.txt:23
DateFormatter\makeNormalYear
makeNormalYear( $iso)
Make a year one from an ISO year, for instance: '400 BC' from '-0399'.
Definition: DateFormatter.php:379
DateFormatter\ISO2
const ISO2
Definition: DateFormatter.php:53
Language
Internationalisation code.
Definition: Language.php:36
DateFormatter\getInstance
static getInstance(Language $lang=null)
Get a DateFormatter object.
Definition: DateFormatter.php:135
DateFormatter
Date formatter, recognises dates in plain text and formats them according to user preferences.
Definition: DateFormatter.php:31
DateFormatter\ALL
const ALL
Definition: DateFormatter.php:46
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:279
DateFormatter\$mTarget
$mTarget
Definition: DateFormatter.php:32