1<?php
2
3declare(strict_types=1);
4/**
5 * SimplePie
6 *
7 * A PHP-Based RSS and Atom Feed Framework.
8 * Takes the hard work out of managing a complete RSS/Atom solution.
9 *
10 * Copyright (c) 2004-2022, Ryan Parman, Sam Sneddon, Ryan McCue, and contributors
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without modification, are
14 * permitted provided that the following conditions are met:
15 *
16 * 	* Redistributions of source code must retain the above copyright notice, this list of
17 * 	  conditions and the following disclaimer.
18 *
19 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
20 * 	  of conditions and the following disclaimer in the documentation and/or other materials
21 * 	  provided with the distribution.
22 *
23 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
24 * 	  to endorse or promote products derived from this software without specific prior
25 * 	  written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
28 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
29 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
30 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 *
37 * @package SimplePie
38 * @copyright 2004-2016 Ryan Parman, Sam Sneddon, Ryan McCue
39 * @author Ryan Parman
40 * @author Sam Sneddon
41 * @author Ryan McCue
42 * @link http://simplepie.org/ SimplePie
43 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
44 */
45
46namespace SimplePie\Parse;
47
48/**
49 * Date Parser
50 *
51 * @package SimplePie
52 * @subpackage Parsing
53 */
54class Date
55{
56    /**
57     * Input data
58     *
59     * @access protected
60     * @var string
61     */
62    public $date;
63
64    /**
65     * List of days, calendar day name => ordinal day number in the week
66     *
67     * @access protected
68     * @var array
69     */
70    public $day = [
71        // English
72        'mon' => 1,
73        'monday' => 1,
74        'tue' => 2,
75        'tuesday' => 2,
76        'wed' => 3,
77        'wednesday' => 3,
78        'thu' => 4,
79        'thursday' => 4,
80        'fri' => 5,
81        'friday' => 5,
82        'sat' => 6,
83        'saturday' => 6,
84        'sun' => 7,
85        'sunday' => 7,
86        // Dutch
87        'maandag' => 1,
88        'dinsdag' => 2,
89        'woensdag' => 3,
90        'donderdag' => 4,
91        'vrijdag' => 5,
92        'zaterdag' => 6,
93        'zondag' => 7,
94        // French
95        'lundi' => 1,
96        'mardi' => 2,
97        'mercredi' => 3,
98        'jeudi' => 4,
99        'vendredi' => 5,
100        'samedi' => 6,
101        'dimanche' => 7,
102        // German
103        'montag' => 1,
104        'mo' => 1,
105        'dienstag' => 2,
106        'di' => 2,
107        'mittwoch' => 3,
108        'mi' => 3,
109        'donnerstag' => 4,
110        'do' => 4,
111        'freitag' => 5,
112        'fr' => 5,
113        'samstag' => 6,
114        'sa' => 6,
115        'sonnabend' => 6,
116        // AFAIK no short form for sonnabend
117        'so' => 7,
118        'sonntag' => 7,
119        // Italian
120        'lunedì' => 1,
121        'martedì' => 2,
122        'mercoledì' => 3,
123        'giovedì' => 4,
124        'venerdì' => 5,
125        'sabato' => 6,
126        'domenica' => 7,
127        // Spanish
128        'lunes' => 1,
129        'martes' => 2,
130        'miércoles' => 3,
131        'jueves' => 4,
132        'viernes' => 5,
133        'sábado' => 6,
134        'domingo' => 7,
135        // Finnish
136        'maanantai' => 1,
137        'tiistai' => 2,
138        'keskiviikko' => 3,
139        'torstai' => 4,
140        'perjantai' => 5,
141        'lauantai' => 6,
142        'sunnuntai' => 7,
143        // Hungarian
144        'hétfő' => 1,
145        'kedd' => 2,
146        'szerda' => 3,
147        'csütörtok' => 4,
148        'péntek' => 5,
149        'szombat' => 6,
150        'vasárnap' => 7,
151        // Greek
152        'Δευ' => 1,
153        'Τρι' => 2,
154        'Τετ' => 3,
155        'Πεμ' => 4,
156        'Παρ' => 5,
157        'Σαβ' => 6,
158        'Κυρ' => 7,
159        // Russian
160        'Пн.' => 1,
161        'Вт.' => 2,
162        'Ср.' => 3,
163        'Чт.' => 4,
164        'Пт.' => 5,
165        'Сб.' => 6,
166        'Вс.' => 7,
167    ];
168
169    /**
170     * List of months, calendar month name => calendar month number
171     *
172     * @access protected
173     * @var array
174     */
175    public $month = [
176        // English
177        'jan' => 1,
178        'january' => 1,
179        'feb' => 2,
180        'february' => 2,
181        'mar' => 3,
182        'march' => 3,
183        'apr' => 4,
184        'april' => 4,
185        'may' => 5,
186        // No long form of May
187        'jun' => 6,
188        'june' => 6,
189        'jul' => 7,
190        'july' => 7,
191        'aug' => 8,
192        'august' => 8,
193        'sep' => 9,
194        'september' => 9,
195        'oct' => 10,
196        'october' => 10,
197        'nov' => 11,
198        'november' => 11,
199        'dec' => 12,
200        'december' => 12,
201        // Dutch
202        'januari' => 1,
203        'februari' => 2,
204        'maart' => 3,
205        'april' => 4,
206        'mei' => 5,
207        'juni' => 6,
208        'juli' => 7,
209        'augustus' => 8,
210        'september' => 9,
211        'oktober' => 10,
212        'november' => 11,
213        'december' => 12,
214        // French
215        'janvier' => 1,
216        'février' => 2,
217        'mars' => 3,
218        'avril' => 4,
219        'mai' => 5,
220        'juin' => 6,
221        'juillet' => 7,
222        'août' => 8,
223        'septembre' => 9,
224        'octobre' => 10,
225        'novembre' => 11,
226        'décembre' => 12,
227        // German
228        'januar' => 1,
229        'jan' => 1,
230        'februar' => 2,
231        'feb' => 2,
232        'märz' => 3,
233        'mär' => 3,
234        'april' => 4,
235        'apr' => 4,
236        'mai' => 5, // no short form for may
237        'juni' => 6,
238        'jun' => 6,
239        'juli' => 7,
240        'jul' => 7,
241        'august' => 8,
242        'aug' => 8,
243        'september' => 9,
244        'sep' => 9,
245        'oktober' => 10,
246        'okt' => 10,
247        'november' => 11,
248        'nov' => 11,
249        'dezember' => 12,
250        'dez' => 12,
251        // Italian
252        'gennaio' => 1,
253        'febbraio' => 2,
254        'marzo' => 3,
255        'aprile' => 4,
256        'maggio' => 5,
257        'giugno' => 6,
258        'luglio' => 7,
259        'agosto' => 8,
260        'settembre' => 9,
261        'ottobre' => 10,
262        'novembre' => 11,
263        'dicembre' => 12,
264        // Spanish
265        'enero' => 1,
266        'febrero' => 2,
267        'marzo' => 3,
268        'abril' => 4,
269        'mayo' => 5,
270        'junio' => 6,
271        'julio' => 7,
272        'agosto' => 8,
273        'septiembre' => 9,
274        'setiembre' => 9,
275        'octubre' => 10,
276        'noviembre' => 11,
277        'diciembre' => 12,
278        // Finnish
279        'tammikuu' => 1,
280        'helmikuu' => 2,
281        'maaliskuu' => 3,
282        'huhtikuu' => 4,
283        'toukokuu' => 5,
284        'kesäkuu' => 6,
285        'heinäkuu' => 7,
286        'elokuu' => 8,
287        'suuskuu' => 9,
288        'lokakuu' => 10,
289        'marras' => 11,
290        'joulukuu' => 12,
291        // Hungarian
292        'január' => 1,
293        'február' => 2,
294        'március' => 3,
295        'április' => 4,
296        'május' => 5,
297        'június' => 6,
298        'július' => 7,
299        'augusztus' => 8,
300        'szeptember' => 9,
301        'október' => 10,
302        'november' => 11,
303        'december' => 12,
304        // Greek
305        'Ιαν' => 1,
306        'Φεβ' => 2,
307        'Μάώ' => 3,
308        'Μαώ' => 3,
309        'Απρ' => 4,
310        'Μάι' => 5,
311        'Μαϊ' => 5,
312        'Μαι' => 5,
313        'Ιούν' => 6,
314        'Ιον' => 6,
315        'Ιούλ' => 7,
316        'Ιολ' => 7,
317        'Αύγ' => 8,
318        'Αυγ' => 8,
319        'Σεπ' => 9,
320        'Οκτ' => 10,
321        'Νοέ' => 11,
322        'Δεκ' => 12,
323        // Russian
324        'Янв' => 1,
325        'января' => 1,
326        'Фев' => 2,
327        'февраля' => 2,
328        'Мар' => 3,
329        'марта' => 3,
330        'Апр' => 4,
331        'апреля' => 4,
332        'Май' => 5,
333        'мая' => 5,
334        'Июн' => 6,
335        'июня' => 6,
336        'Июл' => 7,
337        'июля' => 7,
338        'Авг' => 8,
339        'августа' => 8,
340        'Сен' => 9,
341        'сентября' => 9,
342        'Окт' => 10,
343        'октября' => 10,
344        'Ноя' => 11,
345        'ноября' => 11,
346        'Дек' => 12,
347        'декабря' => 12,
348
349    ];
350
351    /**
352     * List of timezones, abbreviation => offset from UTC
353     *
354     * @access protected
355     * @var array
356     */
357    public $timezone = [
358        'ACDT' => 37800,
359        'ACIT' => 28800,
360        'ACST' => 34200,
361        'ACT' => -18000,
362        'ACWDT' => 35100,
363        'ACWST' => 31500,
364        'AEDT' => 39600,
365        'AEST' => 36000,
366        'AFT' => 16200,
367        'AKDT' => -28800,
368        'AKST' => -32400,
369        'AMDT' => 18000,
370        'AMT' => -14400,
371        'ANAST' => 46800,
372        'ANAT' => 43200,
373        'ART' => -10800,
374        'AZOST' => -3600,
375        'AZST' => 18000,
376        'AZT' => 14400,
377        'BIOT' => 21600,
378        'BIT' => -43200,
379        'BOT' => -14400,
380        'BRST' => -7200,
381        'BRT' => -10800,
382        'BST' => 3600,
383        'BTT' => 21600,
384        'CAST' => 18000,
385        'CAT' => 7200,
386        'CCT' => 23400,
387        'CDT' => -18000,
388        'CEDT' => 7200,
389        'CEST' => 7200,
390        'CET' => 3600,
391        'CGST' => -7200,
392        'CGT' => -10800,
393        'CHADT' => 49500,
394        'CHAST' => 45900,
395        'CIST' => -28800,
396        'CKT' => -36000,
397        'CLDT' => -10800,
398        'CLST' => -14400,
399        'COT' => -18000,
400        'CST' => -21600,
401        'CVT' => -3600,
402        'CXT' => 25200,
403        'DAVT' => 25200,
404        'DTAT' => 36000,
405        'EADT' => -18000,
406        'EAST' => -21600,
407        'EAT' => 10800,
408        'ECT' => -18000,
409        'EDT' => -14400,
410        'EEST' => 10800,
411        'EET' => 7200,
412        'EGT' => -3600,
413        'EKST' => 21600,
414        'EST' => -18000,
415        'FJT' => 43200,
416        'FKDT' => -10800,
417        'FKST' => -14400,
418        'FNT' => -7200,
419        'GALT' => -21600,
420        'GEDT' => 14400,
421        'GEST' => 10800,
422        'GFT' => -10800,
423        'GILT' => 43200,
424        'GIT' => -32400,
425        'GST' => 14400,
426        'GST' => -7200,
427        'GYT' => -14400,
428        'HAA' => -10800,
429        'HAC' => -18000,
430        'HADT' => -32400,
431        'HAE' => -14400,
432        'HAP' => -25200,
433        'HAR' => -21600,
434        'HAST' => -36000,
435        'HAT' => -9000,
436        'HAY' => -28800,
437        'HKST' => 28800,
438        'HMT' => 18000,
439        'HNA' => -14400,
440        'HNC' => -21600,
441        'HNE' => -18000,
442        'HNP' => -28800,
443        'HNR' => -25200,
444        'HNT' => -12600,
445        'HNY' => -32400,
446        'IRDT' => 16200,
447        'IRKST' => 32400,
448        'IRKT' => 28800,
449        'IRST' => 12600,
450        'JFDT' => -10800,
451        'JFST' => -14400,
452        'JST' => 32400,
453        'KGST' => 21600,
454        'KGT' => 18000,
455        'KOST' => 39600,
456        'KOVST' => 28800,
457        'KOVT' => 25200,
458        'KRAST' => 28800,
459        'KRAT' => 25200,
460        'KST' => 32400,
461        'LHDT' => 39600,
462        'LHST' => 37800,
463        'LINT' => 50400,
464        'LKT' => 21600,
465        'MAGST' => 43200,
466        'MAGT' => 39600,
467        'MAWT' => 21600,
468        'MDT' => -21600,
469        'MESZ' => 7200,
470        'MEZ' => 3600,
471        'MHT' => 43200,
472        'MIT' => -34200,
473        'MNST' => 32400,
474        'MSDT' => 14400,
475        'MSST' => 10800,
476        'MST' => -25200,
477        'MUT' => 14400,
478        'MVT' => 18000,
479        'MYT' => 28800,
480        'NCT' => 39600,
481        'NDT' => -9000,
482        'NFT' => 41400,
483        'NMIT' => 36000,
484        'NOVST' => 25200,
485        'NOVT' => 21600,
486        'NPT' => 20700,
487        'NRT' => 43200,
488        'NST' => -12600,
489        'NUT' => -39600,
490        'NZDT' => 46800,
491        'NZST' => 43200,
492        'OMSST' => 25200,
493        'OMST' => 21600,
494        'PDT' => -25200,
495        'PET' => -18000,
496        'PETST' => 46800,
497        'PETT' => 43200,
498        'PGT' => 36000,
499        'PHOT' => 46800,
500        'PHT' => 28800,
501        'PKT' => 18000,
502        'PMDT' => -7200,
503        'PMST' => -10800,
504        'PONT' => 39600,
505        'PST' => -28800,
506        'PWT' => 32400,
507        'PYST' => -10800,
508        'PYT' => -14400,
509        'RET' => 14400,
510        'ROTT' => -10800,
511        'SAMST' => 18000,
512        'SAMT' => 14400,
513        'SAST' => 7200,
514        'SBT' => 39600,
515        'SCDT' => 46800,
516        'SCST' => 43200,
517        'SCT' => 14400,
518        'SEST' => 3600,
519        'SGT' => 28800,
520        'SIT' => 28800,
521        'SRT' => -10800,
522        'SST' => -39600,
523        'SYST' => 10800,
524        'SYT' => 7200,
525        'TFT' => 18000,
526        'THAT' => -36000,
527        'TJT' => 18000,
528        'TKT' => -36000,
529        'TMT' => 18000,
530        'TOT' => 46800,
531        'TPT' => 32400,
532        'TRUT' => 36000,
533        'TVT' => 43200,
534        'TWT' => 28800,
535        'UYST' => -7200,
536        'UYT' => -10800,
537        'UZT' => 18000,
538        'VET' => -14400,
539        'VLAST' => 39600,
540        'VLAT' => 36000,
541        'VOST' => 21600,
542        'VUT' => 39600,
543        'WAST' => 7200,
544        'WAT' => 3600,
545        'WDT' => 32400,
546        'WEST' => 3600,
547        'WFT' => 43200,
548        'WIB' => 25200,
549        'WIT' => 32400,
550        'WITA' => 28800,
551        'WKST' => 18000,
552        'WST' => 28800,
553        'YAKST' => 36000,
554        'YAKT' => 32400,
555        'YAPT' => 36000,
556        'YEKST' => 21600,
557        'YEKT' => 18000,
558    ];
559
560    /**
561     * Cached PCRE for Date::$day
562     *
563     * @access protected
564     * @var string
565     */
566    public $day_pcre;
567
568    /**
569     * Cached PCRE for Date::$month
570     *
571     * @access protected
572     * @var string
573     */
574    public $month_pcre;
575
576    /**
577     * Array of user-added callback methods
578     *
579     * @access private
580     * @var array
581     */
582    public $built_in = [];
583
584    /**
585     * Array of user-added callback methods
586     *
587     * @access private
588     * @var array
589     */
590    public $user = [];
591
592    /**
593     * Create new Date object, and set self::day_pcre,
594     * self::month_pcre, and self::built_in
595     *
596     * @access private
597     */
598    public function __construct()
599    {
600        $this->day_pcre = '(' . implode('|', array_keys($this->day)) . ')';
601        $this->month_pcre = '(' . implode('|', array_keys($this->month)) . ')';
602
603        static $cache;
604        if (!isset($cache[get_class($this)])) {
605            $all_methods = get_class_methods($this);
606
607            foreach ($all_methods as $method) {
608                if (strtolower(substr($method, 0, 5)) === 'date_') {
609                    $cache[get_class($this)][] = $method;
610                }
611            }
612        }
613
614        foreach ($cache[get_class($this)] as $method) {
615            $this->built_in[] = $method;
616        }
617    }
618
619    /**
620     * Get the object
621     *
622     * @access public
623     */
624    public static function get()
625    {
626        static $object;
627        if (!$object) {
628            $object = new Date();
629        }
630        return $object;
631    }
632
633    /**
634     * Parse a date
635     *
636     * @final
637     * @access public
638     * @param string $date Date to parse
639     * @return int Timestamp corresponding to date string, or false on failure
640     */
641    public function parse($date)
642    {
643        foreach ($this->user as $method) {
644            if (($returned = call_user_func($method, $date)) !== false) {
645                return $returned;
646            }
647        }
648
649        foreach ($this->built_in as $method) {
650            if (($returned = call_user_func([$this, $method], $date)) !== false) {
651                return $returned;
652            }
653        }
654
655        return false;
656    }
657
658    /**
659     * Add a callback method to parse a date
660     *
661     * @final
662     * @access public
663     * @param callable $callback
664     */
665    public function add_callback($callback)
666    {
667        if (is_callable($callback)) {
668            $this->user[] = $callback;
669        } else {
670            trigger_error('User-supplied function must be a valid callback', E_USER_WARNING);
671        }
672    }
673
674    /**
675     * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as
676     * well as allowing any of upper or lower case "T", horizontal tabs, or
677     * spaces to be used as the time separator (including more than one))
678     *
679     * @access protected
680     * @return int Timestamp
681     */
682    public function date_w3cdtf($date)
683    {
684        $pcre = <<<'PCRE'
685            /
686            ^
687            (?P<year>[0-9]{4})
688            (?:
689                -?
690                (?P<month>[0-9]{2})
691                (?:
692                    -?
693                    (?P<day>[0-9]{2})
694                    (?:
695                        [Tt\x09\x20]+
696                        (?P<hour>[0-9]{2})
697                        (?:
698                            :?
699                            (?P<minute>[0-9]{2})
700                            (?:
701                                :?
702                                (?P<second>[0-9]{2})
703                                (?:
704                                    .
705                                    (?P<second_fraction>[0-9]*)
706                                )?
707                            )?
708                        )?
709                        (?:
710                            (?P<zulu>Z)
711                            |   (?P<tz_sign>[+\-])
712                                (?P<tz_hour>[0-9]{1,2})
713                                :?
714                                (?P<tz_minute>[0-9]{1,2})
715                        )
716                    )?
717                )?
718            )?
719            $
720            /x
721PCRE;
722        if (preg_match($pcre, $date, $match)) {
723            // Fill in empty matches and convert to proper types.
724            $year = (int) $match['year'];
725            $month = isset($match['month']) ? (int) $match['month'] : 1;
726            $day = isset($match['day']) ? (int) $match['day'] : 1;
727            $hour = isset($match['hour']) ? (int) $match['hour'] : 0;
728            $minute = isset($match['minute']) ? (int) $match['minute'] : 0;
729            $second = isset($match['second']) ? (int) $match['second'] : 0;
730            $second_fraction = isset($match['second_fraction']) ? ((int) $match['second_fraction']) / (10 ** strlen($match['second_fraction'])) : 0;
731            $tz_sign = ($match['tz_sign'] ?? '') === '-' ? -1 : 1;
732            $tz_hour = isset($match['tz_hour']) ? (int) $match['tz_hour'] : 0;
733            $tz_minute = isset($match['tz_minute']) ? (int) $match['tz_minute'] : 0;
734
735            // Numeric timezone
736            $timezone = $tz_hour * 3600;
737            $timezone += $tz_minute * 60;
738            $timezone *= $tz_sign;
739
740            // Convert the number of seconds to an integer, taking decimals into account
741            $second = (int) round($second + $second_fraction);
742
743            return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone;
744        }
745
746        return false;
747    }
748
749    /**
750     * Remove RFC822 comments
751     *
752     * @access protected
753     * @param string $data Data to strip comments from
754     * @return string Comment stripped string
755     */
756    public function remove_rfc2822_comments($string)
757    {
758        $string = (string) $string;
759        $position = 0;
760        $length = strlen($string);
761        $depth = 0;
762
763        $output = '';
764
765        while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) {
766            $output .= substr($string, $position, $pos - $position);
767            $position = $pos + 1;
768            if ($pos === 0 || $string[$pos - 1] !== '\\') {
769                $depth++;
770                while ($depth && $position < $length) {
771                    $position += strcspn($string, '()', $position);
772                    if ($string[$position - 1] === '\\') {
773                        $position++;
774                        continue;
775                    } elseif (isset($string[$position])) {
776                        switch ($string[$position]) {
777                            case '(':
778                                $depth++;
779                                break;
780
781                            case ')':
782                                $depth--;
783                                break;
784                        }
785                        $position++;
786                    } else {
787                        break;
788                    }
789                }
790            } else {
791                $output .= '(';
792            }
793        }
794        $output .= substr($string, $position);
795
796        return $output;
797    }
798
799    /**
800     * Parse RFC2822's date format
801     *
802     * @access protected
803     * @return int Timestamp
804     */
805    public function date_rfc2822($date)
806    {
807        static $pcre;
808        if (!$pcre) {
809            $wsp = '[\x09\x20]';
810            $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)';
811            $optional_fws = $fws . '?';
812            $day_name = $this->day_pcre;
813            $month = $this->month_pcre;
814            $day = '([0-9]{1,2})';
815            $hour = $minute = $second = '([0-9]{2})';
816            $year = '([0-9]{2,4})';
817            $num_zone = '([+\-])([0-9]{2})([0-9]{2})';
818            $character_zone = '([A-Z]{1,5})';
819            $zone = '(?:' . $num_zone . '|' . $character_zone . ')';
820            $pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i';
821        }
822        if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match)) {
823            /*
824            Capturing subpatterns:
825            1: Day name
826            2: Day
827            3: Month
828            4: Year
829            5: Hour
830            6: Minute
831            7: Second
832            8: Timezone ±
833            9: Timezone hours
834            10: Timezone minutes
835            11: Alphabetic timezone
836            */
837
838            // Find the month number
839            $month = $this->month[strtolower($match[3])];
840
841            // Numeric timezone
842            if ($match[8] !== '') {
843                $timezone = $match[9] * 3600;
844                $timezone += $match[10] * 60;
845                if ($match[8] === '-') {
846                    $timezone = 0 - $timezone;
847                }
848            }
849            // Character timezone
850            elseif (isset($this->timezone[strtoupper($match[11])])) {
851                $timezone = $this->timezone[strtoupper($match[11])];
852            }
853            // Assume everything else to be -0000
854            else {
855                $timezone = 0;
856            }
857
858            // Deal with 2/3 digit years
859            if ($match[4] < 50) {
860                $match[4] += 2000;
861            } elseif ($match[4] < 1000) {
862                $match[4] += 1900;
863            }
864
865            // Second is optional, if it is empty set it to zero
866            if ($match[7] !== '') {
867                $second = $match[7];
868            } else {
869                $second = 0;
870            }
871
872            return gmmktime(intval($match[5]), intval($match[6]), intval($second), intval($month), intval($match[2]), intval($match[4])) - $timezone;
873        }
874
875        return false;
876    }
877
878    /**
879     * Parse RFC850's date format
880     *
881     * @access protected
882     * @return int Timestamp
883     */
884    public function date_rfc850($date)
885    {
886        static $pcre;
887        if (!$pcre) {
888            $space = '[\x09\x20]+';
889            $day_name = $this->day_pcre;
890            $month = $this->month_pcre;
891            $day = '([0-9]{1,2})';
892            $year = $hour = $minute = $second = '([0-9]{2})';
893            $zone = '([A-Z]{1,5})';
894            $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i';
895        }
896        if (preg_match($pcre, $date, $match)) {
897            /*
898            Capturing subpatterns:
899            1: Day name
900            2: Day
901            3: Month
902            4: Year
903            5: Hour
904            6: Minute
905            7: Second
906            8: Timezone
907            */
908
909            // Month
910            $month = $this->month[strtolower($match[3])];
911
912            // Character timezone
913            if (isset($this->timezone[strtoupper($match[8])])) {
914                $timezone = $this->timezone[strtoupper($match[8])];
915            }
916            // Assume everything else to be -0000
917            else {
918                $timezone = 0;
919            }
920
921            // Deal with 2 digit year
922            if ($match[4] < 50) {
923                $match[4] += 2000;
924            } else {
925                $match[4] += 1900;
926            }
927
928            return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone;
929        }
930
931        return false;
932    }
933
934    /**
935     * Parse C99's asctime()'s date format
936     *
937     * @access protected
938     * @return int Timestamp
939     */
940    public function date_asctime($date)
941    {
942        static $pcre;
943        if (!$pcre) {
944            $space = '[\x09\x20]+';
945            $wday_name = $this->day_pcre;
946            $mon_name = $this->month_pcre;
947            $day = '([0-9]{1,2})';
948            $hour = $sec = $min = '([0-9]{2})';
949            $year = '([0-9]{4})';
950            $terminator = '\x0A?\x00?';
951            $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i';
952        }
953        if (preg_match($pcre, $date, $match)) {
954            /*
955            Capturing subpatterns:
956            1: Day name
957            2: Month
958            3: Day
959            4: Hour
960            5: Minute
961            6: Second
962            7: Year
963            */
964
965            $month = $this->month[strtolower($match[2])];
966            return gmmktime((int) $match[4], (int) $match[5], (int) $match[6], $month, (int) $match[3], (int) $match[7]);
967        }
968
969        return false;
970    }
971
972    /**
973     * Parse dates using strtotime()
974     *
975     * @access protected
976     * @return int Timestamp
977     */
978    public function date_strtotime($date)
979    {
980        $strtotime = strtotime($date);
981        if ($strtotime === -1 || $strtotime === false) {
982            return false;
983        }
984
985        return $strtotime;
986    }
987}
988
989class_alias('SimplePie\Parse\Date', 'SimplePie_Parse_Date');
990