php DateTime::FromFormat('Ymd','19992323')允许错误的日期

mkh04yzy  于 2023-10-15  发布在  PHP
关注(0)|答案(5)|浏览(91)

以下代码未返回正确的响应

DateTime::createFromFormat('Ymd', '19992323')

返回:

date: 2000-11-23 10:01:41.000000
timezone_type: 3
timezone: America/Toronto

我希望它返回false。请建议

mmvthczy

mmvthczy1#

m允许上级12的数字(php.net)。
m:01至12或1至12。(接受大于12的两位数字,在这种情况下,它们将使年份溢出。例如,使用13表示下一年的1月)
这就是你的约会所发生的事情,它会溢出到下一年。

示例

DateTime::createFromFormat('Ymd', '19991223')
//Expected date: 1999-12-23

DateTime::createFromFormat('Ymd', '19991323')
//Overflow to next year: 2000-01-23

DateTime::createFromFormat('Ymd', '19991423')
//Overflow to next year: 2000-02-23
myzjeezk

myzjeezk2#

虽然可能有点天真,但您应该能够假设,如果您使用格式解析日期,然后使用该格式对其进行格式化,并获得与您输入的字符串相同的字符串,那么日期可能是有效的。

function validateDate(string $format, string $date) {
    if( ($dt = DateTime::createFromFormat($format, $date)) === false ) {
        return false;
    }
    
    return $dt->format($format) === $date;
}

var_dump(
    validateDate('Ymd', '19992323'),
    validateDate('Ymd', '20001123')
);

输出量:

bool(false)
bool(true)

最明显的警告是,并不是所有的解析和格式说明符都是共享的,有些可能会插入解析。接受任意精度但输出固定精度的小数秒。

uttx8gqw

uttx8gqw3#

PHP的DateTime函数和类方法有些宽容,并不意味着要用于严格验证。然而,考虑到你只有一个简单的年、月和日,写一个验证函数是很简单的:

$inputDateString = '19992323';

$date = DateTime::createFromFormat(
    'Ymd',
    filter_var(
        $inputDateString,
        FILTER_CALLBACK,
        [
            'options' => function($date) {
                // Does the input string have the correct date format?
                if(!preg_match('/^(?<Y>[0-9]{4})(?<m>[0-9]{2})(?<d>[0-9]{2})$/', $date, $parts)) {
                    return false;
                }
                // Is the year within a given range?
                // Current range is between 0 and the current server year.
                if(
                    !filter_var(
                        $parts['Y'],
                        FILTER_VALIDATE_INT,
                        [
                            'options' => [
                                // Minimum year
                                'min_range' => 0,
                                // Maximum year
                                'max_range' => date('Y')
                            ]
                        ]
                    )
                ) {
                    return false;
                }
                // Is the month between 1 and 12?
                if(
                    !filter_var(
                        $parts['m'],
                        FILTER_VALIDATE_INT,
                        [
                            'options' => [
                                // January
                                'min_range' => 1,
                                // December
                                'max_range' => 12
                            ]
                        ]
                    )
                ) {
                    return false;
                }
                $lastDayOfMonth =
                    (DateTime::createFromFormat('Ymd', $parts['Y'] . $parts['m'] . '01'))
                        ->modify('last day of this month');
                // Is the day between 1 and the last day of the
                //     given month and year?
                if(
                    !filter_var(
                        $parts['d'],
                        FILTER_VALIDATE_INT,
                        [
                            'options' => [
                                // First day of month
                                'min_range' => 1,
                                // Last day of month
                                'max_range' => $lastDayOfMonth->format('d')
                            ]
                        ]
                    )
                ) {
                    return false;
                }
                return $date;
            }
        ]
    )
);

var_dump($date); // $date is false since month is greater than 12
// $date would be a DateTime object if the input date string were valid.
pw136qt2

pw136qt24#

PHP中的DateTime::DataFromFormat函数并不严格执行格式,它会尝试尽可能多地解析输入字符串。在您的示例中,它将“1999-23-23”解释为“1999-23-23”,然后将其调整为有效日期。如果你想检查日期是否有效,你可以这样做:

$dateString = '19992323';
$format = 'Ymd';
$dateTime = DateTime::createFromFormat($format, $dateString);
if ($dateTime === false || $dateTime->format($format) !== $dateString) {
    echo 'false';
} else {
    echo  $dateTime->format('Y-m-d');
}
t30tvxxf

t30tvxxf5#

这是一个我们必须接受的不幸的设计决定:

$dt = DateTime::createFromFormat('Ymd', '19992323');
$errors = DateTime::getLastErrors();
var_dump($dt, $errors);
object(DateTime)#2 (3) {
  ["date"]=>
  string(26) "2000-11-23 06:50:24.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(3) "UTC"
}
array(4) {
  ["warning_count"]=>
  int(1)
  ["warnings"]=>
  array(1) {
    [8]=>
    string(27) "The parsed date was invalid"
  }
  ["error_count"]=>
  int(0)
  ["errors"]=>
  array(0) {
  }
}

换句话说,要获得严格的字符串解析,您需要额外的手动步骤并检查警告和(我认为,不是100%确定)错误。如果没有这一点,PHP将尽最大努力“修复”输入并产生一个日期,不管是什么(例如。如果没有闰年,就把2月29日变成3月1日)。
我建议你创建一个 Package 器函数,让它为每个无效的输入(nullfalse)返回一个标志值,或者抛出一个异常。一个可能的例子:

function parseDateTime(string $input, string $format = 'Ymd') : DateTime
{
    $dt = DateTime::createFromFormat('Ymd', $input);
    $lastErrors = DateTime::getLastErrors();
    $isInvalid = $dt === false || ($lastErrors !== false && ($lastErrors['error_count'] || $lastErrors['warning_count']));
    if ($isInvalid) {
        throw new \DomainException("Date supplied does not match $format format: $input");
    }
    return $dt;
}

Demo

相关问题