]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/parser/DateFormatter.php
MediaWiki 1.16.0
[autoinstallsdev/mediawiki.git] / includes / parser / DateFormatter.php
1 <?php
2
3 /**
4  * Date formatter, recognises dates in plain text and formats them accoding to user preferences.
5  * @todo preferences, OutputPage
6  * @ingroup Parser
7  */
8 class DateFormatter
9 {
10         var $mSource, $mTarget;
11         var $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD;
12
13         var $regexes, $pDays, $pMonths, $pYears;
14         var $rules, $xMonths, $preferences;
15
16         const ALL = -1;
17         const NONE = 0;
18         const MDY = 1;
19         const DMY = 2;
20         const YMD = 3;
21         const ISO1 = 4;
22         const LASTPREF = 4;
23         const ISO2 = 5;
24         const YDM = 6;
25         const DM = 7;
26         const MD = 8;
27         const LAST = 8;
28
29         /**
30          * @todo document
31          */
32         function DateFormatter() {
33                 global $wgContLang;
34
35                 $this->monthNames = $this->getMonthRegex();
36                 for ( $i=1; $i<=12; $i++ ) {
37                         $this->xMonths[$wgContLang->lc( $wgContLang->getMonthName( $i ) )] = $i;
38                         $this->xMonths[$wgContLang->lc( $wgContLang->getMonthAbbreviation( $i ) )] = $i;
39                 }
40
41                 $this->regexTrail = '(?![a-z])/iu';
42
43                 # Partial regular expressions
44                 $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')\]\]';
45                 $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})\]\]';
46                 $this->prxY = '\[\[(\d{1,4}([ _]BC|))\]\]';
47                 $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})\]\]';
48                 $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})\]\]';
49
50                 # Real regular expressions
51                 $this->regexes[self::DMY] = "/{$this->prxDM}(?: *, *| +){$this->prxY}{$this->regexTrail}";
52                 $this->regexes[self::YDM] = "/{$this->prxY}(?: *, *| +){$this->prxDM}{$this->regexTrail}";
53                 $this->regexes[self::MDY] = "/{$this->prxMD}(?: *, *| +){$this->prxY}{$this->regexTrail}";
54                 $this->regexes[self::YMD] = "/{$this->prxY}(?: *, *| +){$this->prxMD}{$this->regexTrail}";
55                 $this->regexes[self::DM] = "/{$this->prxDM}{$this->regexTrail}";
56                 $this->regexes[self::MD] = "/{$this->prxMD}{$this->regexTrail}";
57                 $this->regexes[self::ISO1] = "/{$this->prxISO1}{$this->regexTrail}";
58                 $this->regexes[self::ISO2] = "/{$this->prxISO2}{$this->regexTrail}";
59
60                 # Extraction keys
61                 # See the comments in replace() for the meaning of the letters
62                 $this->keys[self::DMY] = 'jFY';
63                 $this->keys[self::YDM] = 'Y jF';
64                 $this->keys[self::MDY] = 'FjY';
65                 $this->keys[self::YMD] = 'Y Fj';
66                 $this->keys[self::DM] = 'jF';
67                 $this->keys[self::MD] = 'Fj';
68                 $this->keys[self::ISO1] = 'ymd'; # y means ISO year
69                 $this->keys[self::ISO2] = 'ymd';
70
71                 # Target date formats
72                 $this->targets[self::DMY] = '[[F j|j F]] [[Y]]';
73                 $this->targets[self::YDM] = '[[Y]], [[F j|j F]]';
74                 $this->targets[self::MDY] = '[[F j]], [[Y]]';
75                 $this->targets[self::YMD] = '[[Y]] [[F j]]';
76                 $this->targets[self::DM] = '[[F j|j F]]';
77                 $this->targets[self::MD] = '[[F j]]';
78                 $this->targets[self::ISO1] = '[[Y|y]]-[[F j|m-d]]';
79                 $this->targets[self::ISO2] = '[[y-m-d]]';
80
81                 # Rules
82                 #            pref    source       target
83                 $this->rules[self::DMY][self::MD]       = self::DM;
84                 $this->rules[self::ALL][self::MD]       = self::MD;
85                 $this->rules[self::MDY][self::DM]       = self::MD;
86                 $this->rules[self::ALL][self::DM]       = self::DM;
87                 $this->rules[self::NONE][self::ISO2]    = self::ISO1;
88
89                 $this->preferences = array(
90                         'default' => self::NONE,
91                         'dmy' => self::DMY,
92                         'mdy' => self::MDY,
93                         'ymd' => self::YMD,
94                         'ISO 8601' => self::ISO1,
95                 );
96         }
97
98         /**
99          * Get a DateFormatter object
100          *
101          * @return DateFormatter object
102          */
103         public static function &getInstance() {
104                 global $wgMemc;
105                 static $dateFormatter = false;
106                 if ( !$dateFormatter ) {
107                         $dateFormatter = $wgMemc->get( wfMemcKey( 'dateformatter' ) );
108                         if ( !$dateFormatter ) {
109                                 $dateFormatter = new DateFormatter;
110                                 $wgMemc->set( wfMemcKey( 'dateformatter' ), $dateFormatter, 3600 );
111                         }
112                 }
113                 return $dateFormatter;
114         }
115
116         /**
117          * @param $preference String: User preference
118          * @param $text String: Text to reformat
119          */
120         function reformat( $preference, $text, $options = array('linked') ) {
121         
122                 $linked = in_array( 'linked', $options );
123                 $match_whole = in_array( 'match-whole', $options );
124                 
125                 if ( isset( $this->preferences[$preference] ) ) {
126                         $preference = $this->preferences[$preference];
127                 } else {
128                         $preference = self::NONE;
129                 }
130                 for ( $i=1; $i<=self::LAST; $i++ ) {
131                         $this->mSource = $i;
132                         if ( isset ( $this->rules[$preference][$i] ) ) {
133                                 # Specific rules
134                                 $this->mTarget = $this->rules[$preference][$i];
135                         } elseif ( isset ( $this->rules[self::ALL][$i] ) ) {
136                                 # General rules
137                                 $this->mTarget = $this->rules[self::ALL][$i];
138                         } elseif ( $preference ) {
139                                 # User preference
140                                 $this->mTarget = $preference;
141                         } else {
142                                 # Default
143                                 $this->mTarget = $i;
144                         }
145                         $regex = $this->regexes[$i];
146                         
147                         // Horrible hack
148                         if (!$linked) {
149                                 $regex = str_replace( array( '\[\[', '\]\]' ), '', $regex );
150                         }
151                         
152                         if ($match_whole) {
153                                 // Let's hope this works
154                                 $regex = preg_replace( '!^/!', '/^', $regex );
155                                 $regex = str_replace( $this->regexTrail,
156                                         '$'.$this->regexTrail, $regex );
157                         }
158                         
159                         // Another horrible hack
160                         $this->mLinked = $linked;
161                         $text = preg_replace_callback( $regex, array( &$this, 'replace' ), $text );
162                         unset($this->mLinked);
163                 }
164                 return $text;
165         }
166
167         /**
168          * @param $matches
169          */
170         function replace( $matches ) {
171                 # Extract information from $matches
172                 $linked = true;
173                 if ( isset( $this->mLinked ) )
174                         $linked = $this->mLinked;
175                 
176                 $bits = array();
177                 $key = $this->keys[$this->mSource];
178                 for ( $p=0; $p < strlen($key); $p++ ) {
179                         if ( $key{$p} != ' ' ) {
180                                 $bits[$key{$p}] = $matches[$p+1];
181                         }
182                 }
183                 
184                 return $this->formatDate( $bits, $linked );
185         }
186         
187         function formatDate( $bits, $link = true ) {
188                 $format = $this->targets[$this->mTarget];
189                 
190                 if (!$link) {
191                         // strip piped links
192                         $format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format );
193                         // strip remaining links
194                         $format = str_replace( array( '[[', ']]' ), '', $format );
195                 }
196
197                 # Construct new date
198                 $text = '';
199                 $fail = false;
200                 
201                 // Pre-generate y/Y stuff because we need the year for the <span> title.
202                 if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) )
203                         $bits['y'] = $this->makeIsoYear( $bits['Y'] );
204                 if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) )
205                         $bits['Y'] = $this->makeNormalYear( $bits['y'] );
206                         
207                 if ( !isset( $bits['m'] ) ) {
208                         $m = $this->makeIsoMonth( $bits['F'] );
209                         if ( !$m || $m == '00' ) {
210                                 $fail = true;
211                         } else {
212                                 $bits['m'] = $m;
213                         }
214                 }
215                 
216                 if ( !isset($bits['d']) ) {
217                         $bits['d'] = sprintf( '%02d', $bits['j'] );
218                 }
219
220                 for ( $p=0; $p < strlen( $format ); $p++ ) {
221                         $char = $format{$p};
222                         switch ( $char ) {
223                                 case 'd': # ISO day of month
224                                         $text .= $bits['d'];
225                                         break;
226                                 case 'm': # ISO month
227                                         $text .= $bits['m'];
228                                         break;
229                                 case 'y': # ISO year
230                                         $text .= $bits['y'];
231                                         break;
232                                 case 'j': # ordinary day of month
233                                         if ( !isset($bits['j']) ) {
234                                                 $text .= intval( $bits['d'] );
235                                         } else {
236                                                 $text .= $bits['j'];
237                                         }
238                                         break;
239                                 case 'F': # long month
240                                         if ( !isset( $bits['F'] ) ) {
241                                                 $m = intval($bits['m']);
242                                                 if ( $m > 12 || $m < 1 ) {
243                                                         $fail = true;
244                                                 } else {
245                                                         global $wgContLang;
246                                                         $text .= $wgContLang->getMonthName( $m );
247                                                 }
248                                         } else {
249                                                 $text .= ucfirst( $bits['F'] );
250                                         }
251                                         break;
252                                 case 'Y': # ordinary (optional BC) year
253                                         $text .= $bits['Y'];
254                                         break;
255                                 default:
256                                         $text .= $char;
257                         }
258                 }
259                 if ( $fail ) {
260                         $text = $matches[0];
261                 }
262                 
263                 $isoBits = array();
264                 if ( isset($bits['y']) )
265                         $isoBits[] = $bits['y'];
266                 $isoBits[] = $bits['m'];
267                 $isoBits[] = $bits['d'];
268                 $isoDate = implode( '-', $isoBits );;
269                 
270                 // Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
271                 $text = Html::rawElement( 'span',
272                                         array( 'class' => 'mw-formatted-date', 'title' => $isoDate ), $text );
273                 
274                 return $text;
275         }
276
277         /**
278          * @todo document
279          */
280         function getMonthRegex() {
281                 global $wgContLang;
282                 $names = array();
283                 for( $i = 1; $i <= 12; $i++ ) {
284                         $names[] = $wgContLang->getMonthName( $i );
285                         $names[] = $wgContLang->getMonthAbbreviation( $i );
286                 }
287                 return implode( '|', $names );
288         }
289
290         /**
291          * Makes an ISO month, e.g. 02, from a month name
292          * @param $monthName String: month name
293          * @return string ISO month name
294          */
295         function makeIsoMonth( $monthName ) {
296                 global $wgContLang;
297
298                 $n = $this->xMonths[$wgContLang->lc( $monthName )];
299                 return sprintf( '%02d', $n );
300         }
301
302         /**
303          * @todo document
304          * @param $year String: Year name
305          * @return string ISO year name
306          */
307         function makeIsoYear( $year ) {
308                 # Assumes the year is in a nice format, as enforced by the regex
309                 if ( substr( $year, -2 ) == 'BC' ) {
310                         $num = intval(substr( $year, 0, -3 )) - 1;
311                         # PHP bug note: sprintf( "%04d", -1 ) fails poorly
312                         $text = sprintf( '-%04d', $num );
313
314                 } else {
315                         $text = sprintf( '%04d', $year );
316                 }
317                 return $text;
318         }
319
320         /**
321          * @todo document
322          */
323         function makeNormalYear( $iso ) {
324                 if ( $iso{0} == '-' ) {
325                         $text = (intval( substr( $iso, 1 ) ) + 1) . ' BC';
326                 } else {
327                         $text = intval( $iso );
328                 }
329                 return $text;
330         }
331 }