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