PHP 5.2中的PHP date_parse_from_format()替代方案

时间:2011-07-12 17:13:58

标签: php datetime backwards-compatibility php-5.2

由于date_parse_from_format()仅在PHP 5.3中可用,我需要编写一个模仿PHP 5.2中行为的函数。

是否可以为PHP 5.2编写此函数并使其工作方式与PHP 5.3中的完全相同?

示例:

对于此输入:

<?php
$date = "6.1.2009 13:00+01:00";
print_r(date_parse_from_format("j.n.Y H:iP", $date));
?>

我需要这个输出:

Array
(
    [year] => 2009
    [month] => 1
    [day] => 6
    [hour] => 13
    [minute] => 0
    [second] => 0
    [fraction] => 
    [warning_count] => 0
    [warnings] => Array
        (
        )

    [error_count] => 0
    [errors] => Array
        (
        )

    [is_localtime] => 1
    [zone_type] => 1
    [zone] => -60
    [is_dst] => 
)

5 个答案:

答案 0 :(得分:6)

<?php
function date_parse_from_format($format, $date) {
  $dMask = array(
    'H'=>'hour',
    'i'=>'minute',
    's'=>'second',
    'y'=>'year',
    'm'=>'month',
    'd'=>'day'
  );
  $format = preg_split('//', $format, -1, PREG_SPLIT_NO_EMPTY); 
  $date = preg_split('//', $date, -1, PREG_SPLIT_NO_EMPTY); 
  foreach ($date as $k => $v) {
    if ($dMask[$format[$k]]) $dt[$dMask[$format[$k]]] .= $v;
  }
  return $dt;
}
?>

示例1:

<?php
    print_r(date_parse_from_format('mmddyyyy','03232011');
?>

输出1:

阵 (     [月] =&gt; 03     [day] =&gt; 23     [year] =&gt; 2011 )

示例2:

 <?php
    print_r(date_parse_from_format('yyyy.mm.dd HH:ii:ss','2011.03.23 12:03:00'));
 ?>

输出2:

阵 (     [year] =&gt; 2011     [月] =&gt; 03     [day] =&gt; 23     [小时] =&gt; 12     [分钟] =&gt; 03     [second] =&gt; 00 )

答案 1 :(得分:5)

这是我的改进版本,我认为完整。只考虑错误和警告。

if( !function_exists('date_parse_from_format') ){
    function date_parse_from_format($format, $date) {
        // reverse engineer date formats
        $keys = array(
            'Y' => array('year', '\d{4}'),              //Année sur 4 chiffres
            'y' => array('year', '\d{2}'),              //Année sur 2 chiffres
            'm' => array('month', '\d{2}'),             //Mois au format numérique, avec zéros initiaux
            'n' => array('month', '\d{1,2}'),           //Mois sans les zéros initiaux
            'M' => array('month', '[A-Z][a-z]{3}'),     //Mois, en trois lettres, en anglais
            'F' => array('month', '[A-Z][a-z]{2,8}'),   //Mois, textuel, version longue; en anglais, comme January ou December
            'd' => array('day', '\d{2}'),               //Jour du mois, sur deux chiffres (avec un zéro initial)
            'j' => array('day', '\d{1,2}'),             //Jour du mois sans les zéros initiaux
            'D' => array('day', '[A-Z][a-z]{2}'),       //Jour de la semaine, en trois lettres (et en anglais)
            'l' => array('day', '[A-Z][a-z]{6,9}'),     //Jour de la semaine, textuel, version longue, en anglais
            'u' => array('hour', '\d{1,6}'),            //Microsecondes
            'h' => array('hour', '\d{2}'),              //Heure, au format 12h, avec les zéros initiaux
            'H' => array('hour', '\d{2}'),              //Heure, au format 24h, avec les zéros initiaux
            'g' => array('hour', '\d{1,2}'),            //Heure, au format 12h, sans les zéros initiaux
            'G' => array('hour', '\d{1,2}'),            //Heure, au format 24h, sans les zéros initiaux
            'i' => array('minute', '\d{2}'),            //Minutes avec les zéros initiaux
            's' => array('second', '\d{2}')             //Secondes, avec zéros initiaux
        );

        // convert format string to regex
        $regex = '';
        $chars = str_split($format);
        foreach ( $chars AS $n => $char ) {
            $lastChar = isset($chars[$n-1]) ? $chars[$n-1] : '';
            $skipCurrent = '\\' == $lastChar;
            if ( !$skipCurrent && isset($keys[$char]) ) {
                $regex .= '(?P<'.$keys[$char][0].'>'.$keys[$char][1].')';
            }
            else if ( '\\' == $char ) {
                $regex .= $char;
            }
            else {
                $regex .= preg_quote($char);
            }
        }

        $dt = array();
        // now try to match it
        if( preg_match('#^'.$regex.'$#', $date, $dt) ){
            foreach ( $dt AS $k => $v ){
                if ( is_int($k) ){
                    unset($dt[$k]);
                }
            }
            if( !checkdate($dt['month'], $dt['day'], $dt['year']) ){
                $dt['error_count'] = 1;
            } else {
                $dt['error_count'] = 0;
            }
        }
        else {
            $dt['error_count'] = 1;
        }

        $dt['errors'] = array();
        $dt['fraction'] = '';
        $dt['warning_count'] = 0;
        $dt['warnings'] = array();
        $dt['is_localtime'] = 0;
        $dt['zone_type'] = 0;
        $dt['zone'] = 0;
        $dt['is_dst'] = '';
        return $dt;
    }
}

答案 2 :(得分:2)

如果您希望它与PHP 5.3功能完全相同,那么您将需要大量代码。我会从这样的事情开始:

$format = '\Y: Y-m-d';
var_dump($format);

$date = date($format);
var_dump($date);

// reverse engineer date formats
$keys = array(
    'Y' => array('year', '\d{4}'),
    'm' => array('month', '\d{2}'),
    'd' => array('day', '\d{2}'),
    'j' => array('day', '\d{1,2}'),
    'n' => array('month', '\d{1,2}'),
    'M' => array('month', '[A-Z][a-z]{2}'),
    'F' => array('month', '[A-Z][a-z]{2,8}'),
    'D' => array('day', '[A-Z][a-z]{2}'),
    // etc etc etc
);

// convert format string to regex
$regex = '';
$chars = str_split($format);
foreach ( $chars AS $n => $char ) {
    $lastChar = isset($chars[$n-1]) ? $chars[$n-1] : '';
    $skipCurrent = '\\' == $lastChar;
    if ( !$skipCurrent && isset($keys[$char]) ) {
        $regex .= '(?P<'.$keys[$char][0].'>'.$keys[$char][1].')';
    }
    else if ( '\\' == $char ) {
        $regex .= $char;
    }
    else {
        $regex .= preg_quote($char);
    }
}

var_dump($regex);

// now try to match it
if ( preg_match('#^'.$regex.'$#', $date, $matches) ) {
    foreach ( $matches AS $k => $v ) if ( is_int($k) ) unset($matches[$k]);
    print_r($matches);
}
else {
    echo 'invalid date "'.$date.'" for format "'.$format.'"'."\n";
}

结果:

string(9) "\Y: Y-m-d"
string(13) "Y: 2011-07-12"
string(51) "\Y\: (?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
Array
(
    [year] => 2011
    [month] => 07
    [day] => 12
)

不完整和不完美。

答案 3 :(得分:1)

首先,我要感谢@rudie的回答和@jeremy对他的回答的完善。

我需要更灵活的版本来处理带有TimePicker插件的jQueryUI Datepicker。我还需要它使用\反斜杠转义字符来处理用户输入的奇数时间格式,例如H\h i\m\i\n

这是我的解决方案,基于我在Connections Business Directory WordPress plugin中实施的先前答案。

它更贴近date_parse_from_format()DateTime::createFromFormat()的功能。

希望这有助于某人!

<?php

/**
 * Class cnDate
 */
class cnDate {

    /**
     * Date format characters and their name and regex structure.
     *
     * @access public
     * @since  8.6.4
     *
     * @var array
     */
    protected static $keys = array(
        'Y' => array( 'year', '\d{4}' ),            // Year with 4 Digits
        'y' => array( 'year', '\d{2}' ),            // Year with 2 Digits
        'm' => array( 'month', '\d{2}' ),           // Month with leading 0
        'n' => array( 'month', '\d{1,2}' ),         // Month without the leading 0
        'M' => array( 'month', '[A-Z][a-z]{2}' ),   // Month ABBR 3 letters
        'F' => array( 'month', '[A-Z][a-z]{2,8}' ), // Month Name
        'd' => array( 'day', '\d{2}' ),             // Day with leading 0
        'j' => array( 'day', '\d{1,2}' ),           // Day without leading 0
        'D' => array( 'day', '[A-Z][a-z]{2}' ),     // Day ABBR 3 Letters
        'l' => array( 'day', '[A-Z][a-z]{5,8}' ),   // Day Name
        'h' => array( 'hour', '\d{2}' ),            // Hour 12h formatted, with leading 0
        'H' => array( 'hour', '\d{2}' ),            // Hour 24h formatted, with leading 0
        'g' => array( 'hour', '\d{1,2}' ),          // Hour 12h formatted, without leading 0
        'G' => array( 'hour', '\d{1,2}' ),          // Hour 24h formatted, without leading 0
        'i' => array( 'minute', '\d{2}' ),          // Minutes with leading 0
        's' => array( 'second', '\d{2}' ),          // Seconds with leading 0
        'u' => array( 'hour', '\d{1,6}' ),          // Microseconds
        'a' => array( 'meridiem', '[ap]m' ),        // Lowercase ante meridiem and Post meridiem
        'A' => array( 'meridiem', '[AP]M' ),        // Uppercase ante meridiem and Post meridiem
    );

    /**
     * Create a regex used to parse the supplied datetime format.
     *
     * @access public
     * @since  8.6.4
     *
     * @param string $format The datetime format.
     *
     * @return string
     */
    private static function getFormatRegex( $format ) {

        $keys = self::$keys;

        // Convert format string to regex.
        $regex = '';
        $chars = str_split( $format );

        foreach ( $chars as $n => $char ) {

            $lastChar    = isset( $chars[ $n - 1 ] ) ? $chars[ $n - 1 ] : '';
            $skipCurrent = '\\' == $lastChar;

            if ( ! $skipCurrent && isset( $keys[ $char ] ) ) {

                $regex .= '(?P<' . $keys[ $char ][0] . '>' . $keys[ $char ][1] . ')';

            } elseif ( '\\' == $char || '!' == $char ) {

                /*
                 * No need to add the date format escaping character to the regex since it should not exist in the
                 * supplied datetime string. Including it would cause the preg_match to fail.
                 */
                //$regex .= $char;

            } else {

                $regex .= preg_quote( $char );
            }
        }

        return '#^' . $regex . '$#';
    }

    /**
     * PHP 5.2 does not have a version of @see date_parse_from_format(), this is a mostly PHP 5.2 compatible version.
     *
     * @link http://stackoverflow.com/a/14196482/5351316
     *
     * @access public
     * @since  8.6.4
     *
     * @param string $format The datetime format.
     * @param string $date   The datetime string to parse.
     *
     * @return array
     */
    public static function parseFromFormat( $format, $date ) {

        /** Setup the default values to be returned, matching @see date_parse_from_format() */
        $dt = array(
            'year'          => FALSE,
            'month'         => FALSE,
            'day'           => FALSE,
            'hour'          => FALSE,
            'minute'        => FALSE,
            'second'        => FALSE,
            'fraction'      => FALSE,
            'warning_count' => 0,
            'warnings'      => array(),
            'error_count'   => 0,
            'errors'        => array(),
            'is_localtime'  => FALSE,
            'zone_type'     => 0,
            'zone'          => 0,
            'is_dst'        => '',
        );

        // Now try to match it.
        if ( preg_match( self::getFormatRegex( $format ), $date, $matches ) ) {

            foreach ( $matches as $k => $v ) {

                // Remove unwanted indexes from resulting preg_match.
                if ( is_int( $k ) ) {

                    unset( $matches[ $k ] );
                }

                // Year, month, day, hour, minute, second and fraction should be coerced from string to int.
                if ( in_array( $k, array( 'year', 'month', 'day', 'hour', 'minute', 'second', 'fraction' ) )
                     && is_numeric( $v ) ) {

                    $matches[ $k ] = (int) $v;

                } elseif ( 'month' === $k ) {

                    $parsed = date_parse( $v );
                    $matches[ $k ] = (int) $parsed['month'];

                } elseif ( 'day' === $k ) {

                    $parsed = date_parse( $v );
                    $matches[ $k ] = (int) $parsed['day'];
                }
            }

        } else {

            $dt['error_count'] = 1;
            $dt['errors'][]    = 'Invalid date supplied.'; // @todo match error string from date_parse_from_format()
        }

        return wp_parse_args( $matches, $dt );
    }

    /**
     * PHP 5.2 does not have a version of @see DateTime::createFromFormat(), this is a mostly PHP 5.2 compatible version.
     *
     * @link http://bordoni.me/date_parse_from_format-php-5-2/
     *
     * @access public
     * @since  8.6.4
     *
     * @param  string $format  The datetime format.
     * @param  string $date    The datetime string to parse.
     *
     * @return false|DateTime  Instance of DateTime, false on failure.
     */
    public static function createFromFormat( $format, $date ) {

        $keys  = self::$keys;
        $pos   = strpos( $format, '!' );
        $chars = str_split( $format );

        // Setup default datetime values based on time now or Unix epoch based on if `!` if present in $format.
        if ( FALSE !== $pos ) {

            $datetime = array(
                'year'          => '1970',
                'month'         => '01',
                'day'           => '01',
                'hour'          => '00',
                'minute'        => '00',
                'second'        => '00',
                'fraction'      => '000000',
            );

        } else {

            /** @link http://stackoverflow.com/a/38334226/5351316 */
            list( $usec, $sec ) = explode( ' ', microtime() );

            $datetime = array(
                'year'          => date( 'Y', $sec ),
                'month'         => date( 'm', $sec ),
                'day'           => date( 'd', $sec ),
                'hour'          => date( 'H', $sec ),
                'minute'        => date( 'i', $sec ),
                'second'        => date( 's', $sec ),
                'fraction'      => substr( $usec, 2, 6 ),
            );
        }

        $parsed = self::parseFromFormat( $format, $date );

        foreach ( $chars as $n => $char ) {

            $lastChar    = isset( $chars[ $n - 1 ] ) ? $chars[ $n - 1 ] : '';
            $skipCurrent = '\\' == $lastChar;

            if ( ! $skipCurrent && isset( $keys[ $char ] ) ) {

                // Existing value exists in supplied parsed date.
                if ( $parsed[ $keys[ $char ][0] ] ) {

                    /*
                     * Replace default datetime interval with the parsed datetime interval only if
                     * an `!` was found within the supplied $format and its position is
                     * greater than the current $format character position.
                     */
                    if ( ! ( FALSE !== $pos && $pos > $n ) ) {

                        $datetime[ $keys[ $char ][0] ] = $parsed[ $keys[ $char ][0] ];
                    }
                }
            }
        }

        // Ensure the datetime integers are correctly padded with leading zeros.
        $datetime['month']  = str_pad( $datetime['month'], 2, '0', STR_PAD_LEFT );
        $datetime['day']    = str_pad( $datetime['day'], 2, '0', STR_PAD_LEFT );
        $datetime['hour']   = str_pad( $datetime['hour'], 2, '0', STR_PAD_LEFT );
        $datetime['minute'] = str_pad( $datetime['minute'], 2, '0', STR_PAD_LEFT );
        $datetime['second'] = str_pad( $datetime['second'], 2, '0', STR_PAD_LEFT );

        // Parse the $datetime into a string which can be parsed by DateTime().
        $formatted = strtr( 'year-month-day hour:minute:second.fraction', $datetime );

        // Sanity check to make sure the datetime is valid.
        if ( ! strtotime( $formatted ) ) {

            return FALSE;
        }

        // Return a new DateTime instance.
        return new DateTime( $formatted );
    }
}

答案 4 :(得分:0)

如果您不需要数组的最后4个字段,您可以使用strtotime而不是date_parse_from_format来获得相同的结果。 例如:

$textdate = $date;
$datetime = strtotime($textdate);
$datearray = date_parse($datetime);
print_r($datearray);

这适用于PHP 5.2