[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/parser/ -> DateFormatter.php (source)

   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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1