MediaWiki REL1_37
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
63
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}
Date formatter.
const ALL
Used as a preference ID for rules that apply regardless of preference.
__construct(Language $lang)
int[][] $rules
Array of special rules.
makeNormalYear( $iso)
Make a year from an ISO year, for instance: '400 BC' from '-0399'.
int[] $preferenceIDs
A map of descriptive preference text to internal format ID.
const LAST
The highest ID that is a valid target format.
const NONE
No preference: the date may be left in the same format as the input.
string[] $targetFormats
Format strings similar to those used by date(), indexed by ID.
makeIsoMonth( $monthName)
Makes an ISO month, e.g.
static getInstance(Language $lang=null)
Get a DateFormatter object.
string[] $regexes
Date format regexes indexed the class constants.
int[] $xMonths
Month numbers by lowercase name.
string[] $monthNames
Month names by number.
makeIsoYear( $year)
Make an ISO year from a year name, for instance: '-1199' from '1200 BC'.
reformat( $preference, $text, $options=[])
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:42
MediaWikiServices is the service locator for the application scope of MediaWiki.
$source
if(!isset( $args[0])) $lang