首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 日期時間格式驗證:從正則到 strtotime 最佳實踐

     編輯:PHP 相關知識

前言

日期時間驗證看似簡單,實則暗藏格式檢查、邊界值驗證(閏年、大小月)、時區處理、字串解析容錯等陷阱。本文對比 正則表達式、checkdate()、DateTime 類別、strtotime() 四大流派,給出生產環境可直接用的最佳實踐方案。

四大驗證流派對比

方法格式檢查邊界驗證效能適用場景
正則表達式✅ 強❌ 極難(需超長正則)中固定格式預檢
checkdate()❌ 需自行拆解✅ 內建閏年/月份高純日期驗證
DateTime::createFromFormat()✅ 強✅ 自動驗證中嚴格格式+驗證
strtotime()⚠️ 寬鬆解析✅ 隱含驗證高多格式兼容、快速判斷

方案一:正則預檢 + strtotime 終檢(推薦:平衡嚴格與兼容)

/**
 * 驗證日期時間字串是否合法
 * 支援格式:YYYY-MM-DD HH:II:SS、YYYY-MM-DD、YYYY/MM/DD 等常見格式
 * @return bool
 */
function isValidDateTime(string $dt, string $format = 'Y-m-d H:i:s'): bool {
    // 1. 正則預檢:快速攔截明顯格式錯誤
    if (!preg_match('/^\d{4}[-\/]\d{2}[-\/]\d{2}( \d{2}:\d{2}:\d{2})?$/', $dt)) {
        return false;
    }

    // 2. strtotime 解析:回傳 false 表示無法解析(含邊界錯誤如 2023-02-30)
    $ts = strtotime($dt);
    if ($ts === false) {
        return false;
    }

    // 3. 往返驗證:解析後格式化回字串,比對原始輸入(防止 strtotime 過度容錯,如 "2023-13-01" → 2024-01-01)
    $back = date($format, $ts);
    // 正規化原始輸入格式以便比對
    $normInput = date($format, strtotime($dt));
    return $back === $normInput;
}

// 測試
var_dump(isValidDateTime('2023-02-28 12:30:45')); // true
var_dump(isValidDateTime('2023-02-30'));          // false (2月無30日)
var_dump(isValidDateTime('2023-13-01'));          // false
var_dump(isValidDateTime('2023/12/31'));          // true (支援斜線)
var_dump(isValidDateTime('not-a-date'));          // false

方案二:DateTime::createFromFormat() 嚴格模式(最精確、效能稍低)

function isValidDateTimeStrict(string $dt, string $format = 'Y-m-d H:i:s'): bool {
    $dtObj = DateTime::createFromFormat($format, $dt);
    if (!$dtObj) return false;
    // 檢查是否有解析錯誤(如多餘字元、邊界錯誤)
    $errors = DateTime::getLastErrors();
    return $errors['warning_count'] === 0 && $errors['error_count'] === 0;
}

// 使用
var_dump(isValidDateTimeStrict('2023-02-28 12:30:45')); // true
var_dump(isValidDateTimeStrict('2023-02-30'));          // false
var_dump(isValidDateTimeStrict('2023-02-28 12:30'));    // false (格式不符 H:i:s)

方案三:checkdate() 純日期驗證(最輕量、無時區干擾)

function isValidDate(int $year, int $month, int $day): bool {
    return checkdate($month, $day, $year);
}

// 從字串拆解驗證
function isValidDateFromString(string $dateStr): bool {
    if (!preg_match('/^(\d{4})[-\/](\d{2})[-\/](\d{2})$/', $dateStr, $m)) {
        return false;
    }
    return checkdate((int)$m[2], (int)$m[3], (int)$m[1]);
}

常見坑點與解決

坑點現象解決
strtotime 過度容錯'2023-13-01' → 解析為 2024-01-01往返驗證(方案一步驟 3)或用 DateTime::createFromFormat
時區導致日期偏移UTC 晚間 strtotime('2023-01-01') 可能落在前一日驗證前設定 date_default_timezone_set('Asia/Taipei') 或用 DateTimeImmutable 指定時區
閏年 2/29 驗證失效checkdate(2,29,2023) 應為 falsecheckdate/DateTime 內建正確處理
HTML5 datetime-local 格式'2023-12-31T23:59' (T 分隔)格式字串用 'Y-m-d\TH:i' 或預處理替換 T 為空格

表單驗證整合範例(Laravel 風格規則)

class DateTimeValidator {
    public static function validate(string $input, string $format = 'Y-m-d H:i:s'): array {
        $errors = [];

        // 1. 必填
        if ($input === '') {
            $errors[] = '日期時間不可為空';
            return ['valid' => false, 'errors' => $errors];
        }

        // 2. 格式正則
        if (!preg_match('/^\d{4}[-\/]\d{2}[-\/]\d{2}([ T]\d{2}:\d{2}(:\d{2})?)?$/', $input)) {
            $errors[] = '格式錯誤,請使用 YYYY-MM-DD HH:II:SS';
            return ['valid' => false, 'errors' => $errors];
        }

        // 3. 正規化分隔符
        $normalized = str_replace(['/', 'T'], ['-', ' '], $input);

        // 4. strtotime + 往返驗證
        $ts = strtotime($normalized);
        if ($ts === false) {
            $errors[] = '日期時間不存在(如 2 月 30 日)';
            return ['valid' => false, 'errors' => $errors];
        }

        $back = date($format, $ts);
        $normInput = date($format, $ts);
        if ($back !== $normInput) {
            $errors[] = '日期時間超出合理範圍';
            return ['valid' => false, 'errors' => $errors];
        }

        return ['valid' => true, 'timestamp' => $ts, 'formatted' => $back];
    }
}

// 使用
$result = DateTimeValidator::validate($_POST['birthday'] ?? '');
if ($result['valid']) {
    $dt = new DateTime('@' . $result['timestamp']);
    $dt->setTimezone(new DateTimeZone('Asia/Taipei'));
    // 存入資料庫...
}

資料庫層面驗證(MySQL 模式)

  • DATE:僅儲存日期,自動驗證邊界(2023-02-30 寫入報錯)。
  • DATETIME:不帶時區,範圍 1000-01-01 ~ 9999-12-31。
  • TIMESTAMP:UTC 儲存,範圍 1970-01-01 ~ 2038-01-19(32 位元限制),避免用於長期業務日期。
  • 啟用 sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE 拒絕 0000-00-00、2023-00-00 等非法值。

總結

嚴格業務(生日、合約日期)用 DateTime::createFromFormat();兼容多格式、高吞吐用 正則+strtotime+往返驗證;純日期最輕量用 checkdate()。切記:永遠在應用層驗證,不要只依賴資料庫報錯,才能給使用者友善回饋、保障資料品質。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved