MediaWiki
REL1_19
|
00001 <?php 00013 class DateFormatter 00014 { 00015 var $mSource, $mTarget; 00016 var $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD; 00017 00018 var $regexes, $pDays, $pMonths, $pYears; 00019 var $rules, $xMonths, $preferences; 00020 00021 const ALL = -1; 00022 const NONE = 0; 00023 const MDY = 1; 00024 const DMY = 2; 00025 const YMD = 3; 00026 const ISO1 = 4; 00027 const LASTPREF = 4; 00028 const ISO2 = 5; 00029 const YDM = 6; 00030 const DM = 7; 00031 const MD = 8; 00032 const LAST = 8; 00033 00037 function __construct() { 00038 global $wgContLang; 00039 00040 $this->monthNames = $this->getMonthRegex(); 00041 for ( $i=1; $i<=12; $i++ ) { 00042 $this->xMonths[$wgContLang->lc( $wgContLang->getMonthName( $i ) )] = $i; 00043 $this->xMonths[$wgContLang->lc( $wgContLang->getMonthAbbreviation( $i ) )] = $i; 00044 } 00045 00046 $this->regexTrail = '(?![a-z])/iu'; 00047 00048 # Partial regular expressions 00049 $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')\]\]'; 00050 $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})\]\]'; 00051 $this->prxY = '\[\[(\d{1,4}([ _]BC|))\]\]'; 00052 $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})\]\]'; 00053 $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})\]\]'; 00054 00055 # Real regular expressions 00056 $this->regexes[self::DMY] = "/{$this->prxDM}(?: *, *| +){$this->prxY}{$this->regexTrail}"; 00057 $this->regexes[self::YDM] = "/{$this->prxY}(?: *, *| +){$this->prxDM}{$this->regexTrail}"; 00058 $this->regexes[self::MDY] = "/{$this->prxMD}(?: *, *| +){$this->prxY}{$this->regexTrail}"; 00059 $this->regexes[self::YMD] = "/{$this->prxY}(?: *, *| +){$this->prxMD}{$this->regexTrail}"; 00060 $this->regexes[self::DM] = "/{$this->prxDM}{$this->regexTrail}"; 00061 $this->regexes[self::MD] = "/{$this->prxMD}{$this->regexTrail}"; 00062 $this->regexes[self::ISO1] = "/{$this->prxISO1}{$this->regexTrail}"; 00063 $this->regexes[self::ISO2] = "/{$this->prxISO2}{$this->regexTrail}"; 00064 00065 # Extraction keys 00066 # See the comments in replace() for the meaning of the letters 00067 $this->keys[self::DMY] = 'jFY'; 00068 $this->keys[self::YDM] = 'Y jF'; 00069 $this->keys[self::MDY] = 'FjY'; 00070 $this->keys[self::YMD] = 'Y Fj'; 00071 $this->keys[self::DM] = 'jF'; 00072 $this->keys[self::MD] = 'Fj'; 00073 $this->keys[self::ISO1] = 'ymd'; # y means ISO year 00074 $this->keys[self::ISO2] = 'ymd'; 00075 00076 # Target date formats 00077 $this->targets[self::DMY] = '[[F j|j F]] [[Y]]'; 00078 $this->targets[self::YDM] = '[[Y]], [[F j|j F]]'; 00079 $this->targets[self::MDY] = '[[F j]], [[Y]]'; 00080 $this->targets[self::YMD] = '[[Y]] [[F j]]'; 00081 $this->targets[self::DM] = '[[F j|j F]]'; 00082 $this->targets[self::MD] = '[[F j]]'; 00083 $this->targets[self::ISO1] = '[[Y|y]]-[[F j|m-d]]'; 00084 $this->targets[self::ISO2] = '[[y-m-d]]'; 00085 00086 # Rules 00087 # pref source target 00088 $this->rules[self::DMY][self::MD] = self::DM; 00089 $this->rules[self::ALL][self::MD] = self::MD; 00090 $this->rules[self::MDY][self::DM] = self::MD; 00091 $this->rules[self::ALL][self::DM] = self::DM; 00092 $this->rules[self::NONE][self::ISO2] = self::ISO1; 00093 00094 $this->preferences = array( 00095 'default' => self::NONE, 00096 'dmy' => self::DMY, 00097 'mdy' => self::MDY, 00098 'ymd' => self::YMD, 00099 'ISO 8601' => self::ISO1, 00100 ); 00101 } 00102 00108 public static function &getInstance() { 00109 global $wgMemc; 00110 static $dateFormatter = false; 00111 if ( !$dateFormatter ) { 00112 $dateFormatter = $wgMemc->get( wfMemcKey( 'dateformatter' ) ); 00113 if ( !$dateFormatter ) { 00114 $dateFormatter = new DateFormatter; 00115 $wgMemc->set( wfMemcKey( 'dateformatter' ), $dateFormatter, 3600 ); 00116 } 00117 } 00118 return $dateFormatter; 00119 } 00120 00126 function reformat( $preference, $text, $options = array('linked') ) { 00127 00128 $linked = in_array( 'linked', $options ); 00129 $match_whole = in_array( 'match-whole', $options ); 00130 00131 if ( isset( $this->preferences[$preference] ) ) { 00132 $preference = $this->preferences[$preference]; 00133 } else { 00134 $preference = self::NONE; 00135 } 00136 for ( $i=1; $i<=self::LAST; $i++ ) { 00137 $this->mSource = $i; 00138 if ( isset ( $this->rules[$preference][$i] ) ) { 00139 # Specific rules 00140 $this->mTarget = $this->rules[$preference][$i]; 00141 } elseif ( isset ( $this->rules[self::ALL][$i] ) ) { 00142 # General rules 00143 $this->mTarget = $this->rules[self::ALL][$i]; 00144 } elseif ( $preference ) { 00145 # User preference 00146 $this->mTarget = $preference; 00147 } else { 00148 # Default 00149 $this->mTarget = $i; 00150 } 00151 $regex = $this->regexes[$i]; 00152 00153 // Horrible hack 00154 if (!$linked) { 00155 $regex = str_replace( array( '\[\[', '\]\]' ), '', $regex ); 00156 } 00157 00158 if ($match_whole) { 00159 // Let's hope this works 00160 $regex = preg_replace( '!^/!', '/^', $regex ); 00161 $regex = str_replace( $this->regexTrail, 00162 '$'.$this->regexTrail, $regex ); 00163 } 00164 00165 // Another horrible hack 00166 $this->mLinked = $linked; 00167 $text = preg_replace_callback( $regex, array( &$this, 'replace' ), $text ); 00168 unset($this->mLinked); 00169 } 00170 return $text; 00171 } 00172 00176 function replace( $matches ) { 00177 # Extract information from $matches 00178 $linked = true; 00179 if ( isset( $this->mLinked ) ) 00180 $linked = $this->mLinked; 00181 00182 $bits = array(); 00183 $key = $this->keys[$this->mSource]; 00184 for ( $p=0; $p < strlen($key); $p++ ) { 00185 if ( $key[$p] != ' ' ) { 00186 $bits[$key[$p]] = $matches[$p+1]; 00187 } 00188 } 00189 00190 return $this->formatDate( $bits, $linked ); 00191 } 00192 00193 function formatDate( $bits, $link = true ) { 00194 $format = $this->targets[$this->mTarget]; 00195 00196 if (!$link) { 00197 // strip piped links 00198 $format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format ); 00199 // strip remaining links 00200 $format = str_replace( array( '[[', ']]' ), '', $format ); 00201 } 00202 00203 # Construct new date 00204 $text = ''; 00205 $fail = false; 00206 00207 // Pre-generate y/Y stuff because we need the year for the <span> title. 00208 if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) 00209 $bits['y'] = $this->makeIsoYear( $bits['Y'] ); 00210 if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) 00211 $bits['Y'] = $this->makeNormalYear( $bits['y'] ); 00212 00213 if ( !isset( $bits['m'] ) ) { 00214 $m = $this->makeIsoMonth( $bits['F'] ); 00215 if ( !$m || $m == '00' ) { 00216 $fail = true; 00217 } else { 00218 $bits['m'] = $m; 00219 } 00220 } 00221 00222 if ( !isset($bits['d']) ) { 00223 $bits['d'] = sprintf( '%02d', $bits['j'] ); 00224 } 00225 00226 for ( $p=0; $p < strlen( $format ); $p++ ) { 00227 $char = $format[$p]; 00228 switch ( $char ) { 00229 case 'd': # ISO day of month 00230 $text .= $bits['d']; 00231 break; 00232 case 'm': # ISO month 00233 $text .= $bits['m']; 00234 break; 00235 case 'y': # ISO year 00236 $text .= $bits['y']; 00237 break; 00238 case 'j': # ordinary day of month 00239 if ( !isset($bits['j']) ) { 00240 $text .= intval( $bits['d'] ); 00241 } else { 00242 $text .= $bits['j']; 00243 } 00244 break; 00245 case 'F': # long month 00246 if ( !isset( $bits['F'] ) ) { 00247 $m = intval($bits['m']); 00248 if ( $m > 12 || $m < 1 ) { 00249 $fail = true; 00250 } else { 00251 global $wgContLang; 00252 $text .= $wgContLang->getMonthName( $m ); 00253 } 00254 } else { 00255 $text .= ucfirst( $bits['F'] ); 00256 } 00257 break; 00258 case 'Y': # ordinary (optional BC) year 00259 $text .= $bits['Y']; 00260 break; 00261 default: 00262 $text .= $char; 00263 } 00264 } 00265 if ( $fail ) { 00266 $text = $matches[0]; 00267 } 00268 00269 $isoBits = array(); 00270 if ( isset($bits['y']) ) 00271 $isoBits[] = $bits['y']; 00272 $isoBits[] = $bits['m']; 00273 $isoBits[] = $bits['d']; 00274 $isoDate = implode( '-', $isoBits ); 00275 00276 // Output is not strictly HTML (it's wikitext), but <span> is whitelisted. 00277 $text = Html::rawElement( 'span', 00278 array( 'class' => 'mw-formatted-date', 'title' => $isoDate ), $text ); 00279 00280 return $text; 00281 } 00282 00286 function getMonthRegex() { 00287 global $wgContLang; 00288 $names = array(); 00289 for( $i = 1; $i <= 12; $i++ ) { 00290 $names[] = $wgContLang->getMonthName( $i ); 00291 $names[] = $wgContLang->getMonthAbbreviation( $i ); 00292 } 00293 return implode( '|', $names ); 00294 } 00295 00301 function makeIsoMonth( $monthName ) { 00302 global $wgContLang; 00303 00304 $n = $this->xMonths[$wgContLang->lc( $monthName )]; 00305 return sprintf( '%02d', $n ); 00306 } 00307 00313 function makeIsoYear( $year ) { 00314 # Assumes the year is in a nice format, as enforced by the regex 00315 if ( substr( $year, -2 ) == 'BC' ) { 00316 $num = intval(substr( $year, 0, -3 )) - 1; 00317 # PHP bug note: sprintf( "%04d", -1 ) fails poorly 00318 $text = sprintf( '-%04d', $num ); 00319 00320 } else { 00321 $text = sprintf( '%04d', $year ); 00322 } 00323 return $text; 00324 } 00325 00329 function makeNormalYear( $iso ) { 00330 if ( $iso[0] == '-' ) { 00331 $text = (intval( substr( $iso, 1 ) ) + 1) . ' BC'; 00332 } else { 00333 $text = intval( $iso ); 00334 } 00335 return $text; 00336 } 00337 }