[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Date formatter 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup Parser 22 */ 23 24 /** 25 * Date formatter, recognises dates in plain text and formats them according to user preferences. 26 * @todo preferences, OutputPage 27 * @ingroup Parser 28 */ 29 class DateFormatter { 30 public $mSource, $mTarget; 31 public $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD; 32 33 public $regexes, $pDays, $pMonths, $pYears; 34 public $rules, $xMonths, $preferences; 35 36 protected $lang; 37 38 const ALL = -1; 39 const NONE = 0; 40 const MDY = 1; 41 const DMY = 2; 42 const YMD = 3; 43 const ISO1 = 4; 44 const LASTPREF = 4; 45 const ISO2 = 5; 46 const YDM = 6; 47 const DM = 7; 48 const MD = 8; 49 const LAST = 8; 50 51 /** 52 * @param Language $lang In which language to format the date 53 */ 54 public function __construct( Language $lang ) { 55 $this->lang = $lang; 56 57 $this->monthNames = $this->getMonthRegex(); 58 for ( $i = 1; $i <= 12; $i++ ) { 59 $this->xMonths[$this->lang->lc( $this->lang->getMonthName( $i ) )] = $i; 60 $this->xMonths[$this->lang->lc( $this->lang->getMonthAbbreviation( $i ) )] = $i; 61 } 62 63 $this->regexTrail = '(?![a-z])/iu'; 64 65 # Partial regular expressions 66 $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')\]\]'; 67 $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})\]\]'; 68 $this->prxY = '\[\[(\d{1,4}([ _]BC|))\]\]'; 69 $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})\]\]'; 70 $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})\]\]'; 71 72 # Real regular expressions 73 $this->regexes[self::DMY] = "/{$this->prxDM}(?: *, *| +){$this->prxY}{$this->regexTrail}"; 74 $this->regexes[self::YDM] = "/{$this->prxY}(?: *, *| +){$this->prxDM}{$this->regexTrail}"; 75 $this->regexes[self::MDY] = "/{$this->prxMD}(?: *, *| +){$this->prxY}{$this->regexTrail}"; 76 $this->regexes[self::YMD] = "/{$this->prxY}(?: *, *| +){$this->prxMD}{$this->regexTrail}"; 77 $this->regexes[self::DM] = "/{$this->prxDM}{$this->regexTrail}"; 78 $this->regexes[self::MD] = "/{$this->prxMD}{$this->regexTrail}"; 79 $this->regexes[self::ISO1] = "/{$this->prxISO1}{$this->regexTrail}"; 80 $this->regexes[self::ISO2] = "/{$this->prxISO2}{$this->regexTrail}"; 81 82 # Extraction keys 83 # See the comments in replace() for the meaning of the letters 84 $this->keys[self::DMY] = 'jFY'; 85 $this->keys[self::YDM] = 'Y jF'; 86 $this->keys[self::MDY] = 'FjY'; 87 $this->keys[self::YMD] = 'Y Fj'; 88 $this->keys[self::DM] = 'jF'; 89 $this->keys[self::MD] = 'Fj'; 90 $this->keys[self::ISO1] = 'ymd'; # y means ISO year 91 $this->keys[self::ISO2] = 'ymd'; 92 93 # Target date formats 94 $this->targets[self::DMY] = '[[F j|j F]] [[Y]]'; 95 $this->targets[self::YDM] = '[[Y]], [[F j|j F]]'; 96 $this->targets[self::MDY] = '[[F j]], [[Y]]'; 97 $this->targets[self::YMD] = '[[Y]] [[F j]]'; 98 $this->targets[self::DM] = '[[F j|j F]]'; 99 $this->targets[self::MD] = '[[F j]]'; 100 $this->targets[self::ISO1] = '[[Y|y]]-[[F j|m-d]]'; 101 $this->targets[self::ISO2] = '[[y-m-d]]'; 102 103 # Rules 104 # pref source target 105 $this->rules[self::DMY][self::MD] = self::DM; 106 $this->rules[self::ALL][self::MD] = self::MD; 107 $this->rules[self::MDY][self::DM] = self::MD; 108 $this->rules[self::ALL][self::DM] = self::DM; 109 $this->rules[self::NONE][self::ISO2] = self::ISO1; 110 111 $this->preferences = array( 112 'default' => self::NONE, 113 'dmy' => self::DMY, 114 'mdy' => self::MDY, 115 'ymd' => self::YMD, 116 'ISO 8601' => self::ISO1, 117 ); 118 } 119 120 /** 121 * Get a DateFormatter object 122 * 123 * @param Language|string|null $lang In which language to format the date 124 * Defaults to the site content language 125 * @return DateFormatter 126 */ 127 public static function &getInstance( $lang = null ) { 128 global $wgMemc, $wgContLang; 129 static $dateFormatter = false; 130 $lang = $lang ? wfGetLangObj( $lang ) : $wgContLang; 131 $key = wfMemcKey( 'dateformatter', $lang->getCode() ); 132 if ( !$dateFormatter ) { 133 $dateFormatter = $wgMemc->get( $key ); 134 if ( !$dateFormatter ) { 135 $dateFormatter = new DateFormatter( $lang ); 136 $wgMemc->set( $key, $dateFormatter, 3600 ); 137 } 138 } 139 return $dateFormatter; 140 } 141 142 /** 143 * @param string $preference User preference 144 * @param string $text Text to reformat 145 * @param array $options Array can contain 'linked' and/or 'match-whole' 146 * 147 * @return string 148 */ 149 public function reformat( $preference, $text, $options = array( 'linked' ) ) { 150 $linked = in_array( 'linked', $options ); 151 $match_whole = in_array( 'match-whole', $options ); 152 153 if ( isset( $this->preferences[$preference] ) ) { 154 $preference = $this->preferences[$preference]; 155 } else { 156 $preference = self::NONE; 157 } 158 for ( $i = 1; $i <= self::LAST; $i++ ) { 159 $this->mSource = $i; 160 if ( isset( $this->rules[$preference][$i] ) ) { 161 # Specific rules 162 $this->mTarget = $this->rules[$preference][$i]; 163 } elseif ( isset( $this->rules[self::ALL][$i] ) ) { 164 # General rules 165 $this->mTarget = $this->rules[self::ALL][$i]; 166 } elseif ( $preference ) { 167 # User preference 168 $this->mTarget = $preference; 169 } else { 170 # Default 171 $this->mTarget = $i; 172 } 173 $regex = $this->regexes[$i]; 174 175 // Horrible hack 176 if ( !$linked ) { 177 $regex = str_replace( array( '\[\[', '\]\]' ), '', $regex ); 178 } 179 180 if ( $match_whole ) { 181 // Let's hope this works 182 $regex = preg_replace( '!^/!', '/^', $regex ); 183 $regex = str_replace( $this->regexTrail, 184 '$' . $this->regexTrail, $regex ); 185 } 186 187 // Another horrible hack 188 $this->mLinked = $linked; 189 $text = preg_replace_callback( $regex, array( &$this, 'replace' ), $text ); 190 unset( $this->mLinked ); 191 } 192 return $text; 193 } 194 195 /** 196 * @param array $matches 197 * @return string 198 */ 199 public function replace( $matches ) { 200 # Extract information from $matches 201 $linked = true; 202 if ( isset( $this->mLinked ) ) { 203 $linked = $this->mLinked; 204 } 205 206 $bits = array(); 207 $key = $this->keys[$this->mSource]; 208 $keyLength = strlen( $key ); 209 for ( $p = 0; $p < $keyLength; $p++ ) { 210 if ( $key[$p] != ' ' ) { 211 $bits[$key[$p]] = $matches[$p + 1]; 212 } 213 } 214 215 return $this->formatDate( $bits, $linked ); 216 } 217 218 /** 219 * @param array $bits 220 * @param bool $link 221 * @return string 222 */ 223 public function formatDate( $bits, $link = true ) { 224 $format = $this->targets[$this->mTarget]; 225 226 if ( !$link ) { 227 // strip piped links 228 $format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format ); 229 // strip remaining links 230 $format = str_replace( array( '[[', ']]' ), '', $format ); 231 } 232 233 # Construct new date 234 $text = ''; 235 $fail = false; 236 237 // Pre-generate y/Y stuff because we need the year for the <span> title. 238 if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) { 239 $bits['y'] = $this->makeIsoYear( $bits['Y'] ); 240 } 241 if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) { 242 $bits['Y'] = $this->makeNormalYear( $bits['y'] ); 243 } 244 245 if ( !isset( $bits['m'] ) ) { 246 $m = $this->makeIsoMonth( $bits['F'] ); 247 if ( !$m || $m == '00' ) { 248 $fail = true; 249 } else { 250 $bits['m'] = $m; 251 } 252 } 253 254 if ( !isset( $bits['d'] ) ) { 255 $bits['d'] = sprintf( '%02d', $bits['j'] ); 256 } 257 258 $formatLength = strlen( $format ); 259 for ( $p = 0; $p < $formatLength; $p++ ) { 260 $char = $format[$p]; 261 switch ( $char ) { 262 case 'd': # ISO day of month 263 $text .= $bits['d']; 264 break; 265 case 'm': # ISO month 266 $text .= $bits['m']; 267 break; 268 case 'y': # ISO year 269 $text .= $bits['y']; 270 break; 271 case 'j': # ordinary day of month 272 if ( !isset( $bits['j'] ) ) { 273 $text .= intval( $bits['d'] ); 274 } else { 275 $text .= $bits['j']; 276 } 277 break; 278 case 'F': # long month 279 if ( !isset( $bits['F'] ) ) { 280 $m = intval( $bits['m'] ); 281 if ( $m > 12 || $m < 1 ) { 282 $fail = true; 283 } else { 284 $text .= $this->lang->getMonthName( $m ); 285 } 286 } else { 287 $text .= ucfirst( $bits['F'] ); 288 } 289 break; 290 case 'Y': # ordinary (optional BC) year 291 $text .= $bits['Y']; 292 break; 293 default: 294 $text .= $char; 295 } 296 } 297 if ( $fail ) { 298 /** @todo FIXME: $matches doesn't exist here, what's expected? */ 299 $text = $matches[0]; 300 } 301 302 $isoBits = array(); 303 if ( isset( $bits['y'] ) ) { 304 $isoBits[] = $bits['y']; 305 } 306 $isoBits[] = $bits['m']; 307 $isoBits[] = $bits['d']; 308 $isoDate = implode( '-', $isoBits ); 309 310 // Output is not strictly HTML (it's wikitext), but <span> is whitelisted. 311 $text = Html::rawElement( 'span', 312 array( 'class' => 'mw-formatted-date', 'title' => $isoDate ), $text ); 313 314 return $text; 315 } 316 317 /** 318 * @todo document 319 * @return string 320 */ 321 public function getMonthRegex() { 322 $names = array(); 323 for ( $i = 1; $i <= 12; $i++ ) { 324 $names[] = $this->lang->getMonthName( $i ); 325 $names[] = $this->lang->getMonthAbbreviation( $i ); 326 } 327 return implode( '|', $names ); 328 } 329 330 /** 331 * Makes an ISO month, e.g. 02, from a month name 332 * @param string $monthName Month name 333 * @return string ISO month name 334 */ 335 public function makeIsoMonth( $monthName ) { 336 $n = $this->xMonths[$this->lang->lc( $monthName )]; 337 return sprintf( '%02d', $n ); 338 } 339 340 /** 341 * @todo document 342 * @param string $year Year name 343 * @return string ISO year name 344 */ 345 public function makeIsoYear( $year ) { 346 # Assumes the year is in a nice format, as enforced by the regex 347 if ( substr( $year, -2 ) == 'BC' ) { 348 $num = intval( substr( $year, 0, -3 ) ) - 1; 349 # PHP bug note: sprintf( "%04d", -1 ) fails poorly 350 $text = sprintf( '-%04d', $num ); 351 352 } else { 353 $text = sprintf( '%04d', $year ); 354 } 355 return $text; 356 } 357 358 /** 359 * @todo document 360 * @param string $iso 361 * @return int|string 362 */ 363 public function makeNormalYear( $iso ) { 364 if ( $iso[0] == '-' ) { 365 $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC'; 366 } else { 367 $text = intval( $iso ); 368 } 369 return $text; 370 } 371 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |