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-2012, 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.
37 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
39 * @author Geoffrey Sneddon
41 * @link http://simplepie.org/ SimplePie
42 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
52 class SimplePie_Parse_Date
63 * List of days, calendar day name => ordinal day number in the week
152 * List of months, calendar month name => calendar month number
168 // No long form of May
297 * List of timezones, abbreviation => offset from UTC
302 var $timezone = array(
506 * Cached PCRE for SimplePie_Parse_Date::$day
514 * Cached PCRE for SimplePie_Parse_Date::$month
522 * Array of user-added callback methods
527 var $built_in = array();
530 * Array of user-added callback methods
538 * Create new SimplePie_Parse_Date object, and set self::day_pcre,
539 * self::month_pcre, and self::built_in
543 public function __construct()
545 $this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')';
546 $this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')';
549 if (!isset($cache[get_class($this)]))
551 $all_methods = get_class_methods($this);
553 foreach ($all_methods as $method)
555 if (strtolower(substr($method, 0, 5)) === 'date_')
557 $cache[get_class($this)][] = $method;
562 foreach ($cache[get_class($this)] as $method)
564 $this->built_in[] = $method;
573 public static function get()
578 $object = new SimplePie_Parse_Date;
588 * @param string $date Date to parse
589 * @return int Timestamp corresponding to date string, or false on failure
591 public function parse($date)
593 foreach ($this->user as $method)
595 if (($returned = call_user_func($method, $date)) !== false)
601 foreach ($this->built_in as $method)
603 if (($returned = call_user_func(array($this, $method), $date)) !== false)
613 * Add a callback method to parse a date
617 * @param callable $callback
619 public function add_callback($callback)
621 if (is_callable($callback))
623 $this->user[] = $callback;
627 trigger_error('User-supplied function must be a valid callback', E_USER_WARNING);
632 * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as
633 * well as allowing any of upper or lower case "T", horizontal tabs, or
634 * spaces to be used as the time seperator (including more than one))
637 * @return int Timestamp
639 public function date_w3cdtf($date)
644 $year = '([0-9]{4})';
645 $month = $day = $hour = $minute = $second = '([0-9]{2})';
646 $decimal = '([0-9]*)';
647 $zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))';
648 $pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/';
650 if (preg_match($pcre, $date, $match))
653 Capturing subpatterns:
660 7: Decimal fraction of a second
667 // Fill in empty matches
668 for ($i = count($match); $i <= 3; $i++)
673 for ($i = count($match); $i <= 7; $i++)
679 if (isset($match[9]) && $match[9] !== '')
681 $timezone = $match[10] * 3600;
682 $timezone += $match[11] * 60;
683 if ($match[9] === '-')
685 $timezone = 0 - $timezone;
693 // Convert the number of seconds to an integer, taking decimals into account
694 $second = round($match[6] + $match[7] / pow(10, strlen($match[7])));
696 return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone;
705 * Remove RFC822 comments
708 * @param string $data Data to strip comments from
709 * @return string Comment stripped string
711 public function remove_rfc2822_comments($string)
713 $string = (string) $string;
715 $length = strlen($string);
720 while ($position < $length && ($pos = strpos($string, '(', $position)) !== false)
722 $output .= substr($string, $position, $pos - $position);
723 $position = $pos + 1;
724 if ($string[$pos - 1] !== '\\')
727 while ($depth && $position < $length)
729 $position += strcspn($string, '()', $position);
730 if ($string[$position - 1] === '\\')
735 elseif (isset($string[$position]))
737 switch ($string[$position])
760 $output .= substr($string, $position);
766 * Parse RFC2822's date format
769 * @return int Timestamp
771 public function date_rfc2822($date)
777 $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)';
778 $optional_fws = $fws . '?';
779 $day_name = $this->day_pcre;
780 $month = $this->month_pcre;
781 $day = '([0-9]{1,2})';
782 $hour = $minute = $second = '([0-9]{2})';
783 $year = '([0-9]{2,4})';
784 $num_zone = '([+\-])([0-9]{2})([0-9]{2})';
785 $character_zone = '([A-Z]{1,5})';
786 $zone = '(?:' . $num_zone . '|' . $character_zone . ')';
787 $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';
789 if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match))
792 Capturing subpatterns:
803 11: Alphabetic timezone
806 // Find the month number
807 $month = $this->month[strtolower($match[3])];
810 if ($match[8] !== '')
812 $timezone = $match[9] * 3600;
813 $timezone += $match[10] * 60;
814 if ($match[8] === '-')
816 $timezone = 0 - $timezone;
819 // Character timezone
820 elseif (isset($this->timezone[strtoupper($match[11])]))
822 $timezone = $this->timezone[strtoupper($match[11])];
824 // Assume everything else to be -0000
830 // Deal with 2/3 digit years
835 elseif ($match[4] < 1000)
840 // Second is optional, if it is empty set it to zero
841 if ($match[7] !== '')
850 return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone;
859 * Parse RFC850's date format
862 * @return int Timestamp
864 public function date_rfc850($date)
869 $space = '[\x09\x20]+';
870 $day_name = $this->day_pcre;
871 $month = $this->month_pcre;
872 $day = '([0-9]{1,2})';
873 $year = $hour = $minute = $second = '([0-9]{2})';
874 $zone = '([A-Z]{1,5})';
875 $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i';
877 if (preg_match($pcre, $date, $match))
880 Capturing subpatterns:
892 $month = $this->month[strtolower($match[3])];
894 // Character timezone
895 if (isset($this->timezone[strtoupper($match[8])]))
897 $timezone = $this->timezone[strtoupper($match[8])];
899 // Assume everything else to be -0000
905 // Deal with 2 digit year
915 return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone;
924 * Parse C99's asctime()'s date format
927 * @return int Timestamp
929 public function date_asctime($date)
934 $space = '[\x09\x20]+';
935 $wday_name = $this->day_pcre;
936 $mon_name = $this->month_pcre;
937 $day = '([0-9]{1,2})';
938 $hour = $sec = $min = '([0-9]{2})';
939 $year = '([0-9]{4})';
940 $terminator = '\x0A?\x00?';
941 $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i';
943 if (preg_match($pcre, $date, $match))
946 Capturing subpatterns:
956 $month = $this->month[strtolower($match[2])];
957 return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]);
966 * Parse dates using strtotime()
969 * @return int Timestamp
971 public function date_strtotime($date)
973 $strtotime = strtotime($date);
974 if ($strtotime === -1 || $strtotime === false)