5 * A PHP-Based RSS and Atom Feed Framework.
6 * Takes the hard work out of managing a complete RSS/Atom solution.
8 * Copyright (c) 2004-2016, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
11 * Redistribution and use in source and binary forms, with or without modification, are
12 * permitted provided that the following conditions are met:
14 * * Redistributions of source code must retain the above copyright notice, this list of
15 * conditions and the following disclaimer.
17 * * Redistributions in binary form must reproduce the above copyright notice, this list
18 * of conditions and the following disclaimer in the documentation and/or other materials
19 * provided with the distribution.
21 * * Neither the name of the SimplePie Team nor the names of its contributors may be used
22 * to endorse or promote products derived from this software without specific prior
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
26 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
28 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
32 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
36 * @copyright 2004-2016 Ryan Parman, Geoffrey Sneddon, Ryan McCue
38 * @author Geoffrey Sneddon
40 * @link http://simplepie.org/ SimplePie
41 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
51 class SimplePie_Parse_Date
62 * List of days, calendar day name => ordinal day number in the week
151 * List of months, calendar month name => calendar month number
167 // No long form of May
296 * List of timezones, abbreviation => offset from UTC
301 var $timezone = array(
505 * Cached PCRE for SimplePie_Parse_Date::$day
513 * Cached PCRE for SimplePie_Parse_Date::$month
521 * Array of user-added callback methods
526 var $built_in = array();
529 * Array of user-added callback methods
537 * Create new SimplePie_Parse_Date object, and set self::day_pcre,
538 * self::month_pcre, and self::built_in
542 public function __construct()
544 $this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')';
545 $this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')';
548 if (!isset($cache[get_class($this)]))
550 $all_methods = get_class_methods($this);
552 foreach ($all_methods as $method)
554 if (strtolower(substr($method, 0, 5)) === 'date_')
556 $cache[get_class($this)][] = $method;
561 foreach ($cache[get_class($this)] as $method)
563 $this->built_in[] = $method;
572 public static function get()
577 $object = new SimplePie_Parse_Date;
587 * @param string $date Date to parse
588 * @return int Timestamp corresponding to date string, or false on failure
590 public function parse($date)
592 foreach ($this->user as $method)
594 if (($returned = call_user_func($method, $date)) !== false)
600 foreach ($this->built_in as $method)
602 if (($returned = call_user_func(array($this, $method), $date)) !== false)
612 * Add a callback method to parse a date
616 * @param callback $callback
618 public function add_callback($callback)
620 if (is_callable($callback))
622 $this->user[] = $callback;
626 trigger_error('User-supplied function must be a valid callback', E_USER_WARNING);
631 * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as
632 * well as allowing any of upper or lower case "T", horizontal tabs, or
633 * spaces to be used as the time seperator (including more than one))
636 * @return int Timestamp
638 public function date_w3cdtf($date)
643 $year = '([0-9]{4})';
644 $month = $day = $hour = $minute = $second = '([0-9]{2})';
645 $decimal = '([0-9]*)';
646 $zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))';
647 $pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/';
649 if (preg_match($pcre, $date, $match))
652 Capturing subpatterns:
659 7: Decimal fraction of a second
666 // Fill in empty matches
667 for ($i = count($match); $i <= 3; $i++)
672 for ($i = count($match); $i <= 7; $i++)
678 if (isset($match[9]) && $match[9] !== '')
680 $timezone = $match[10] * 3600;
681 $timezone += $match[11] * 60;
682 if ($match[9] === '-')
684 $timezone = 0 - $timezone;
692 // Convert the number of seconds to an integer, taking decimals into account
693 $second = round($match[6] + $match[7] / pow(10, strlen($match[7])));
695 return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone;
704 * Remove RFC822 comments
707 * @param string $data Data to strip comments from
708 * @return string Comment stripped string
710 public function remove_rfc2822_comments($string)
712 $string = (string) $string;
714 $length = strlen($string);
719 while ($position < $length && ($pos = strpos($string, '(', $position)) !== false)
721 $output .= substr($string, $position, $pos - $position);
722 $position = $pos + 1;
723 if ($pos === 0 || $string[$pos - 1] !== '\\')
726 while ($depth && $position < $length)
728 $position += strcspn($string, '()', $position);
729 if ($string[$position - 1] === '\\')
734 elseif (isset($string[$position]))
736 switch ($string[$position])
759 $output .= substr($string, $position);
765 * Parse RFC2822's date format
768 * @return int Timestamp
770 public function date_rfc2822($date)
776 $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)';
777 $optional_fws = $fws . '?';
778 $day_name = $this->day_pcre;
779 $month = $this->month_pcre;
780 $day = '([0-9]{1,2})';
781 $hour = $minute = $second = '([0-9]{2})';
782 $year = '([0-9]{2,4})';
783 $num_zone = '([+\-])([0-9]{2})([0-9]{2})';
784 $character_zone = '([A-Z]{1,5})';
785 $zone = '(?:' . $num_zone . '|' . $character_zone . ')';
786 $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';
788 if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match))
791 Capturing subpatterns:
802 11: Alphabetic timezone
805 // Find the month number
806 $month = $this->month[strtolower($match[3])];
809 if ($match[8] !== '')
811 $timezone = $match[9] * 3600;
812 $timezone += $match[10] * 60;
813 if ($match[8] === '-')
815 $timezone = 0 - $timezone;
818 // Character timezone
819 elseif (isset($this->timezone[strtoupper($match[11])]))
821 $timezone = $this->timezone[strtoupper($match[11])];
823 // Assume everything else to be -0000
829 // Deal with 2/3 digit years
834 elseif ($match[4] < 1000)
839 // Second is optional, if it is empty set it to zero
840 if ($match[7] !== '')
849 return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone;
858 * Parse RFC850's date format
861 * @return int Timestamp
863 public function date_rfc850($date)
868 $space = '[\x09\x20]+';
869 $day_name = $this->day_pcre;
870 $month = $this->month_pcre;
871 $day = '([0-9]{1,2})';
872 $year = $hour = $minute = $second = '([0-9]{2})';
873 $zone = '([A-Z]{1,5})';
874 $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i';
876 if (preg_match($pcre, $date, $match))
879 Capturing subpatterns:
891 $month = $this->month[strtolower($match[3])];
893 // Character timezone
894 if (isset($this->timezone[strtoupper($match[8])]))
896 $timezone = $this->timezone[strtoupper($match[8])];
898 // Assume everything else to be -0000
904 // Deal with 2 digit year
914 return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone;
923 * Parse C99's asctime()'s date format
926 * @return int Timestamp
928 public function date_asctime($date)
933 $space = '[\x09\x20]+';
934 $wday_name = $this->day_pcre;
935 $mon_name = $this->month_pcre;
936 $day = '([0-9]{1,2})';
937 $hour = $sec = $min = '([0-9]{2})';
938 $year = '([0-9]{4})';
939 $terminator = '\x0A?\x00?';
940 $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i';
942 if (preg_match($pcre, $date, $match))
945 Capturing subpatterns:
955 $month = $this->month[strtolower($match[2])];
956 return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]);
965 * Parse dates using strtotime()
968 * @return int Timestamp
970 public function date_strtotime($date)
972 $strtotime = strtotime($date);
973 if ($strtotime === -1 || $strtotime === false)