* Cached PCRE for Date::$day
* Cached PCRE for Date::$month
* Array of user-added callback methods
* Array of user-added callback methods
* @var array<callable(string): (int|false)>
* Create new Date object, and set self::day_pcre,
* self::month_pcre, and self::built_in
public function __construct()
$this->day_pcre = '(' . implode('|', array_keys($this->day)) . ')';
$this->month_pcre = '(' . implode('|', array_keys($this->month)) . ')';
if (!isset($cache[get_class($this)])) {
$all_methods = get_class_methods($this);
foreach ($all_methods as $method) {
if (strtolower(substr($method, 0, 5)) === 'date_') {
$cache[get_class($this)][] = $method;
foreach ($cache[get_class($this)] as $method) {
$this->built_in[] = $method;
public static function get()
* @param string $date Date to parse
* @return int|false Timestamp corresponding to date string, or false on failure
public function parse(string $date)
foreach ($this->user as $method) {
if (($returned = call_user_func($method, $date)) !== false) {
foreach ($this->built_in as $method) {
// TODO: we should really check this in constructor but that would require private properties.
/** @var callable(string): (int|false) */
$callable = [$this, $method];
if (($returned = call_user_func($callable, $date)) !== false) {
* Add a callback method to parse a date
* @param callable $callback
public function add_callback(callable $callback)
$this->user[] = $callback;
* Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as
* well as allowing any of upper or lower case "T", horizontal tabs, or
* spaces to be used as the time separator (including more than one))
* @return int|false Timestamp
public function date_w3cdtf(string $date)
(?P<second_fraction>[0-9]*)
(?P<tz_minute>[0-9]{1,2})
if (preg_match($pcre, $date, $match)) {
// Fill in empty matches and convert to proper types.
$year = (int) $match['year'];
$month = isset($match['month']) ? (int) $match['month'] : 1;
$day = isset($match['day']) ? (int) $match['day'] : 1;
$hour = isset($match['hour']) ? (int) $match['hour'] : 0;
$minute = isset($match['minute']) ? (int) $match['minute'] : 0;
$second = isset($match['second']) ? (int) $match['second'] : 0;
$second_fraction = isset($match['second_fraction']) ? ((int) $match['second_fraction']) / (10 ** strlen($match['second_fraction'])) : 0;
$tz_sign = ($match['tz_sign'] ?? '') === '-' ? -1 : 1;
$tz_hour = isset($match['tz_hour']) ? (int) $match['tz_hour'] : 0;
$tz_minute = isset($match['tz_minute']) ? (int) $match['tz_minute'] : 0;
$timezone = $tz_hour * 3600;
$timezone += $tz_minute * 60;
// Convert the number of seconds to an integer, taking decimals into account
$second = (int) round($second + $second_fraction);
return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone;
* @param string $string Data to strip comments from
* @return string Comment stripped string
public function remove_rfc2822_comments(string $string)
$length = strlen($string);
while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) {
$output .= substr($string, $position, $pos - $position);
if ($pos === 0 || $string[$pos - 1] !== '\\') {
while ($depth && $position < $length) {
$position += strcspn($string, '()', $position);
if ($string[$position - 1] === '\\') {
} elseif (isset($string[$position])) {
switch ($string[$position]) {
$output .= substr($string, $position);
* Parse RFC2822's date format
* @return int|false Timestamp
public function date_rfc2822(string $date)
$fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)';
$optional_fws = $fws . '?';
$day_name = $this->day_pcre;
$month = $this->month_pcre;
$hour = $minute = $second = '([0-9]{2})';
$num_zone = '([+\-])([0-9]{2})([0-9]{2})';
$character_zone = '([A-Z]{1,5})';
$zone = '(?:' . $num_zone . '|' . $character_zone . ')';
$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';
if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match)) {
$month = $this->month[strtolower($match[3])];
$minute = (int) $match[6];
// Second is optional, if it is empty set it to zero
$second = (int) $match[7];
$tz_hour = (int) $match[9];
$tz_minute = (int) $match[10];
$tz_code = isset($match[11]) ? strtoupper($match[11]) : '';
$timezone = $tz_hour * 3600;
$timezone += $tz_minute * 60;
$timezone = 0 - $timezone;
elseif (isset($this->timezone[$tz_code])) {
$timezone = $this->timezone[$tz_code];
// Assume everything else to be -0000
// Deal with 2/3 digit years
} elseif ($year < 1000) {
return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone;
* Parse RFC850's date format
* @return int|false Timestamp
public function date_rfc850(string $date)
$day_name = $this->day_pcre;
$month = $this->month_pcre;
$year = $hour = $minute = $second = '([0-9]{2})';
$pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i';
if (preg_match($pcre, $date, $match)) {
$month = $this->month[strtolower($match[3])];
$minute = (int) $match[6];
// Second is optional, if it is empty set it to zero
$second = (int) $match[7];
$tz_code = strtoupper($match[8]);
if (isset($this->timezone[$tz_code])) {
$timezone = $this->timezone[$tz_code];
// Assume everything else to be -0000
// Deal with 2 digit year
return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone;
* Parse C99's asctime()'s date format
* @return int|false Timestamp
public function date_asctime(string $date)
$wday_name = $this->day_pcre;
$mon_name = $this->month_pcre;
$hour = $sec = $min = '([0-9]{2})';
$terminator = '\x0A?\x00?';
$pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i';
if (preg_match($pcre, $date, $match)) {
$month = $this->month[strtolower($match[2])];
return gmmktime((int) $match[4], (int) $match[5], (int) $match[6], $month, (int) $match[3], (int) $match[7]);
* Parse dates using strtotime()
* @return int|false Timestamp
public function date_strtotime(string $date)
$strtotime = strtotime($date);
if ($strtotime === -1 || $strtotime === false) {
class_alias('SimplePie\Parse\Date', 'SimplePie_Parse_Date');