MediaWiki  master
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 
62  private $preferenceIDs;
63 
65  private $targetFormats;
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 }
int [] $xMonths
Month numbers by lowercase name.
reformat( $preference, $text, $options=[])
const YMD
e.g.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
if(!isset( $args[0])) $lang
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
$source
makeNormalYear( $iso)
Make a year from an ISO year, for instance: &#39;400 BC&#39; from &#39;-0399&#39;.
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
const YDM
e.g.
static getInstance(Language $lang=null)
Get a DateFormatter object.
const ALL
Used as a preference ID for rules that apply regardless of preference.
getMonthAbbreviation( $key)
Definition: Language.php:998
__construct(Language $lang)
int [] $preferenceIDs
A map of descriptive preference text to internal format ID.
const LAST
The highest ID that is a valid target format.
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:1971
const MDY
e.g.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:767
string [] $monthNames
Month names by number.
makeIsoYear( $year)
Make an ISO year from a year name, for instance: &#39;-1199&#39; from &#39;1200 BC&#39;.
string [] $regexes
Date format regexes indexed the class constants.
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
makeIsoMonth( $monthName)
Makes an ISO month, e.g.
const LASTPREF
The highest ID that is a valid user preference.
const DM
e.g.
const MD
e.g.
int [][] $rules
Array of special rules.
Date formatter.
const ISO
e.g.
string [] $targetFormats
Format strings similar to those used by date(), indexed by ID.
const DMY
e.g.
const NONE
No preference: the date may be left in the same format as the input.
getMonthName( $key)
Definition: Language.php:971