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  private const ALL = -1;
69 
71  private const NONE = 0;
72 
74  private const MDY = 1;
75 
77  private const DMY = 2;
78 
80  private const YMD = 3;
81 
83  private const ISO = 4;
84 
86  private const YDM = 5;
87 
89  private const DM = 6;
90 
92  private const MD = 7;
93 
95  private const LAST = 7;
96 
100  public function __construct( Language $lang ) {
101  $monthRegexParts = [];
102  for ( $i = 1; $i <= 12; $i++ ) {
103  $monthName = $lang->getMonthName( $i );
104  $monthAbbrev = $lang->getMonthAbbreviation( $i );
105  $this->monthNames[$i] = $monthName;
106  $monthRegexParts[] = preg_quote( $monthName, '/' );
107  $monthRegexParts[] = preg_quote( $monthAbbrev, '/' );
108  $this->xMonths[mb_strtolower( $monthName )] = $i;
109  $this->xMonths[mb_strtolower( $monthAbbrev )] = $i;
110  }
111 
112  // Partial regular expressions
113  $monthNames = implode( '|', $monthRegexParts );
114  $dm = "(?<day>\d{1,2})[ _](?<monthName>{$monthNames})";
115  $md = "(?<monthName>{$monthNames})[ _](?<day>\d{1,2})";
116  $y = '(?<year>\d{1,4}([ _]BC|))';
117  $iso = '(?<isoYear>-?\d{4})-(?<isoMonth>\d{2})-(?<isoDay>\d{2})';
118 
119  $this->regexes = [
120  self::DMY => "/^{$dm}(?: *, *| +){$y}$/iu",
121  self::YDM => "/^{$y}(?: *, *| +){$dm}$/iu",
122  self::MDY => "/^{$md}(?: *, *| +){$y}$/iu",
123  self::YMD => "/^{$y}(?: *, *| +){$md}$/iu",
124  self::DM => "/^{$dm}$/iu",
125  self::MD => "/^{$md}$/iu",
126  self::ISO => "/^{$iso}$/iu",
127  ];
128 
129  // Target date formats
130  $this->targetFormats = [
131  self::DMY => 'j F Y',
132  self::YDM => 'Y, j F',
133  self::MDY => 'F j, Y',
134  self::YMD => 'Y F j',
135  self::DM => 'j F',
136  self::MD => 'F j',
137  self::ISO => 'y-m-d',
138  ];
139 
140  // Rules
141  // pref source target
142  $this->rules[self::DMY][self::MD] = self::DM;
143  $this->rules[self::ALL][self::MD] = self::MD;
144  $this->rules[self::MDY][self::DM] = self::MD;
145  $this->rules[self::ALL][self::DM] = self::DM;
146  $this->rules[self::NONE][self::ISO] = self::ISO;
147 
148  $this->preferenceIDs = [
149  'default' => self::NONE,
150  'dmy' => self::DMY,
151  'mdy' => self::MDY,
152  'ymd' => self::YMD,
153  'ISO 8601' => self::ISO,
154  ];
155  }
156 
166  public static function getInstance( Language $lang = null ) {
167  $lang = $lang ?? MediaWikiServices::getInstance()->getContentLanguage();
168  return MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
169  }
170 
180  public function reformat( $preference, $text, $options = [] ) {
181  if ( isset( $this->preferenceIDs[$preference] ) ) {
182  $preference = $this->preferenceIDs[$preference];
183  } else {
184  $preference = self::NONE;
185  }
186  for ( $source = 1; $source <= self::LAST; $source++ ) {
187  if ( isset( $this->rules[$preference][$source] ) ) {
188  # Specific rules
189  $target = $this->rules[$preference][$source];
190  } elseif ( isset( $this->rules[self::ALL][$source] ) ) {
191  # General rules
192  $target = $this->rules[self::ALL][$source];
193  } elseif ( $preference ) {
194  # User preference
195  $target = $preference;
196  } else {
197  # Default
198  $target = $source;
199  }
200  $regex = $this->regexes[$source];
201 
202  $text = preg_replace_callback( $regex,
203  function ( $match ) use ( $target ) {
204  $format = $this->targetFormats[$target];
205 
206  $text = '';
207 
208  // Pre-generate y/Y stuff because we need the year for the <span> title.
209  if ( !isset( $match['isoYear'] ) && isset( $match['year'] ) ) {
210  $match['isoYear'] = $this->makeIsoYear( $match['year'] );
211  }
212  if ( !isset( $match['year'] ) && isset( $match['isoYear'] ) ) {
213  $match['year'] = $this->makeNormalYear( $match['isoYear'] );
214  }
215 
216  if ( !isset( $match['isoMonth'] ) ) {
217  $m = $this->makeIsoMonth( $match['monthName'] );
218  if ( $m === false ) {
219  // Fail
220  return $match[0];
221  } else {
222  $match['isoMonth'] = $m;
223  }
224  }
225 
226  if ( !isset( $match['isoDay'] ) ) {
227  $match['isoDay'] = sprintf( '%02d', $match['day'] );
228  }
229 
230  $formatLength = strlen( $format );
231  for ( $p = 0; $p < $formatLength; $p++ ) {
232  $char = $format[$p];
233  switch ( $char ) {
234  case 'd': // ISO day of month
235  $text .= $match['isoDay'];
236  break;
237  case 'm': // ISO month
238  $text .= $match['isoMonth'];
239  break;
240  case 'y': // ISO year
241  $text .= $match['isoYear'];
242  break;
243  case 'j': // ordinary day of month
244  if ( !isset( $match['day'] ) ) {
245  $text .= intval( $match['isoDay'] );
246  } else {
247  $text .= $match['day'];
248  }
249  break;
250  case 'F': // long month
251  $m = intval( $match['isoMonth'] );
252  if ( $m > 12 || $m < 1 ) {
253  // Fail
254  return $match[0];
255  } else {
256  $text .= $this->monthNames[$m];
257  }
258  break;
259  case 'Y': // ordinary (optional BC) year
260  $text .= $match['year'];
261  break;
262  default:
263  $text .= $char;
264  }
265  }
266 
267  $isoBits = [];
268  if ( isset( $match['isoYear'] ) ) {
269  $isoBits[] = $match['isoYear'];
270  }
271  $isoBits[] = $match['isoMonth'];
272  $isoBits[] = $match['isoDay'];
273  $isoDate = implode( '-', $isoBits );
274 
275  // Output is not strictly HTML (it's wikitext), but <span> is allowed.
276  return Html::rawElement( 'span',
277  [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
278  }, $text
279  );
280  }
281  return $text;
282  }
283 
289  private function makeIsoMonth( $monthName ) {
290  $isoMonth = $this->xMonths[mb_strtolower( $monthName )] ?? false;
291  if ( $isoMonth === false ) {
292  return false;
293  }
294  return sprintf( '%02d', $isoMonth );
295  }
296 
302  private function makeIsoYear( $year ) {
303  // Assumes the year is in a nice format, as enforced by the regex
304  if ( substr( $year, -2 ) == 'BC' ) {
305  $num = intval( substr( $year, 0, -3 ) ) - 1;
306  // PHP bug note: sprintf( "%04d", -1 ) fails poorly
307  $text = sprintf( '-%04d', $num );
308  } else {
309  $text = sprintf( '%04d', $year );
310  }
311  return $text;
312  }
313 
320  private function makeNormalYear( $iso ) {
321  if ( $iso[0] == '-' ) {
322  $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC';
323  } else {
324  $text = intval( $iso );
325  }
326  return $text;
327  }
328 }
DateFormatter\ISO
const ISO
e.g.
Definition: DateFormatter.php:83
DateFormatter\$preferenceIDs
int[] $preferenceIDs
A map of descriptive preference text to internal format ID.
Definition: DateFormatter.php:62
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:200
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
DateFormatter\$rules
int[][] $rules
Array of special rules.
Definition: DateFormatter.php:47
DateFormatter\$xMonths
int[] $xMonths
Month numbers by lowercase name.
Definition: DateFormatter.php:52
DateFormatter\$monthNames
string[] $monthNames
Month names by number.
Definition: DateFormatter.php:57
DateFormatter\makeIsoMonth
makeIsoMonth( $monthName)
Makes an ISO month, e.g.
Definition: DateFormatter.php:289
DateFormatter\YMD
const YMD
e.g.
Definition: DateFormatter.php:80
DateFormatter\NONE
const NONE
No preference: the date may be left in the same format as the input.
Definition: DateFormatter.php:71
DateFormatter\MD
const MD
e.g.
Definition: DateFormatter.php:92
DateFormatter\DM
const DM
e.g.
Definition: DateFormatter.php:89
DateFormatter\__construct
__construct(Language $lang)
Definition: DateFormatter.php:100
DateFormatter\$targetFormats
string[] $targetFormats
Format strings similar to those used by date(), indexed by ID.
Definition: DateFormatter.php:65
DateFormatter\DMY
const DMY
e.g.
Definition: DateFormatter.php:77
DateFormatter\MDY
const MDY
e.g.
Definition: DateFormatter.php:74
DateFormatter\reformat
reformat( $preference, $text, $options=[])
Definition: DateFormatter.php:180
DateFormatter\YDM
const YDM
e.g.
Definition: DateFormatter.php:86
DateFormatter\makeIsoYear
makeIsoYear( $year)
Make an ISO year from a year name, for instance: '-1199' from '1200 BC'.
Definition: DateFormatter.php:302
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
$source
$source
Definition: mwdoc-filter.php:34
DateFormatter\LAST
const LAST
The highest ID that is a valid target format.
Definition: DateFormatter.php:95
DateFormatter\makeNormalYear
makeNormalYear( $iso)
Make a year from an ISO year, for instance: '400 BC' from '-0399'.
Definition: DateFormatter.php:320
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:42
DateFormatter\getInstance
static getInstance(Language $lang=null)
Get a DateFormatter object.
Definition: DateFormatter.php:166
DateFormatter
Date formatter.
Definition: DateFormatter.php:37
DateFormatter\ALL
const ALL
Used as a preference ID for rules that apply regardless of preference.
Definition: DateFormatter.php:68
DateFormatter\$regexes
string[] $regexes
Date format regexes indexed the class constants.
Definition: DateFormatter.php:39