PHP date_create_immutable_from_format()函数

date_create_immutable_from_format() 函数根据指定的格式从日期字符串创建一个新的不可变 DateTime 对象,返回 DateTimeImmutable 对象。与 date_create_immutable() 不同,这个函数允许你指定日期字符串的确切格式,而不是依赖于 strtotime() 的自动解析。

这个函数是 DateTimeImmutable::createFromFormat() 静态方法的过程化别名,推荐在需要精确控制日期格式解析的场景中使用。

语法

date_create_immutable_from_format(string $format, string $datetime [, DateTimeZone $timezone = null]) : DateTimeImmutable|false

参数说明

参数 默认值 描述
format

必需。 日期/时间格式字符串,指定 $datetime 参数的格式。

格式字符与 date() 函数使用的相同。

datetime

必需。 日期/时间字符串,必须符合 $format 参数指定的格式。

timezone null

可选。 DateTimeZone 对象,表示时区。

如果为 null,则使用当前默认时区。

返回值

成功时返回一个新的 DateTimeImmutable 对象,失败时返回 false

常用格式字符

字符 描述 示例
d 月份中的第几天,有前导零的 2 位数字 01 到 31
m 数字表示的月份,有前导零 01 到 12
Y 4 位数字完整表示的年份 例如:1999 或 2023
y 2 位数字表示的年份 例如:99 或 23
H 24 小时格式,有前导零 00 到 23
i 有前导零的分钟数 00 到 59
s 有前导零的秒数 00 到 59
U Unix 时间戳 例如:1617180000
c ISO 8601 日期 2023-12-25T14:30:00+00:00
r RFC 2822 格式日期 Mon, 25 Dec 2023 14:30:00 +0000

示例

示例 1:基本用法

<?php
// 从标准格式创建不可变对象
$date = date_create_immutable_from_format('Y-m-d H:i:s', '2023-12-25 14:30:00');

if ($date !== false) {
    echo "日期: " . $date->format('Y-m-d H:i:s') . "<br>";
    echo "星期: " . $date->format('l') . "<br>";
    echo "时间戳: " . $date->getTimestamp();
} else {
    echo "日期解析失败";
}

// 输出:
// 日期: 2023-12-25 14:30:00
// 星期: Monday
// 时间戳: 1703500200
?>

示例 2:解析不同格式的日期

<?php
// 解析不同格式的日期
$formats_and_dates = [
    ['d/m/Y', '25/12/2023'],
    ['Y年m月d日', '2023年12月25日'],
    ['m-d-Y', '12-25-2023'],
    ['Ymd His', '20231225 143000'],
    ['l, F j, Y', 'Monday, December 25, 2023']
];

foreach ($formats_and_dates as $item) {
    list($format, $date_str) = $item;
    $date = date_create_immutable_from_format($format, $date_str);

    if ($date !== false) {
        echo "格式: '{$format}', 字符串: '{$date_str}'<br>";
        echo "解析结果: " . $date->format('Y-m-d H:i:s') . "<br><br>";
    } else {
        echo "格式: '{$format}', 字符串: '{$date_str}' - 解析失败<br><br>";
    }
}

// 输出类似:
// 格式: 'd/m/Y', 字符串: '25/12/2023'
// 解析结果: 2023-12-25 00:00:00
//
// 格式: 'Y年m月d日', 字符串: '2023年12月25日'
// 解析结果: 2023-12-25 00:00:00
//
// 格式: 'm-d-Y', 字符串: '12-25-2023'
// 解析结果: 2023-12-25 00:00:00
//
// 格式: 'Ymd His', 字符串: '20231225 143000'
// 解析结果: 2023-12-25 14:30:00
//
// 格式: 'l, F j, Y', 字符串: 'Monday, December 25, 2023'
// 解析结果: 2023-12-25 00:00:00
?>

示例 3:处理不完整的日期

<?php
// 处理不完整的日期(自动填充缺失部分)
echo "<h4>自动填充缺失部分:</h4>";

// 只提供日期,没有时间
$date1 = date_create_immutable_from_format('Y-m-d', '2023-12-25');
echo "只提供日期: " . ($date1 !== false ? $date1->format('Y-m-d H:i:s') : '失败') . "<br>";

// 只提供年月
$date2 = date_create_immutable_from_format('Y-m', '2023-12');
echo "只提供年月: " . ($date2 !== false ? $date2->format('Y-m-d H:i:s') : '失败') . "<br>";

// 只提供年份
$date3 = date_create_immutable_from_format('Y', '2023');
echo "只提供年份: " . ($date3 !== false ? $date3->format('Y-m-d H:i:s') : '失败') . "<br>";

// 提供日期和小时分钟
$date4 = date_create_immutable_from_format('Y-m-d H:i', '2023-12-25 14:30');
echo "提供日期和小时分钟: " . ($date4 !== false ? $date4->format('Y-m-d H:i:s') : '失败') . "<br>";

echo "<h4>使用 ! 前缀避免自动填充:</h4>";
// 使用 ! 前缀表示重置所有字段
$date5 = date_create_immutable_from_format('!Y', '2023');
echo "只解析年份(使用!): " . ($date5 !== false ? $date5->format('Y-m-d H:i:s') : '失败') . "<br>";

// 输出类似:
// 自动填充缺失部分:
// 只提供日期: 2023-12-25 00:00:00
// 只提供年月: 2023-12-01 00:00:00
// 只提供年份: 2023-01-01 00:00:00
// 提供日期和小时分钟: 2023-12-25 14:30:00
//
// 使用 ! 前缀避免自动填充:
// 只解析年份(使用!): 2023-01-01 00:00:00
?>

示例 4:错误处理和验证

<?php
// 错误处理和验证
function parseDateWithValidation($format, $date_str) {
    $date = date_create_immutable_from_format($format, $date_str);

    if ($date === false) {
        $errors = DateTimeImmutable::getLastErrors();
        return [
            'success' => false,
            'error' => '解析失败',
            'warnings' => $errors['warnings'] ?? [],
            'errors' => $errors['errors'] ?? []
        ];
    }

    // 验证解析后的日期是否与原始字符串匹配
    $formatted_back = $date->format($format);
    $is_valid = ($formatted_back === $date_str);

    return [
        'success' => true,
        'date' => $date,
        'is_valid' => $is_valid,
        'formatted_back' => $formatted_back
    ];
}

// 测试
$test_cases = [
    ['Y-m-d', '2023-12-25'],
    ['Y-m-d', '2023-13-45'], // 无效日期
    ['d/m/Y', '25/12/2023'],
    ['d/m/Y', '32/12/2023'], // 无效日
    ['H:i:s', '14:30:00'],
    ['Y-m-d H:i:s', '2023-12-25 14:30:00']
];

foreach ($test_cases as $test) {
    list($format, $date_str) = $test;
    $result = parseDateWithValidation($format, $date_str);

    echo "<strong>测试:</strong> 格式 '{$format}', 字符串 '{$date_str}'<br>";

    if ($result['success']) {
        echo "<span style='color: green;'>✓ 解析成功</span><br>";
        echo "日期: " . $result['date']->format('Y-m-d H:i:s') . "<br>";
        echo "验证: " . ($result['is_valid'] ? '通过' : '不匹配') . "<br>";
    } else {
        echo "<span style='color: red;'>✗ 解析失败</span><br>";

        if (!empty($result['warnings'])) {
            echo "警告: " . implode(', ', $result['warnings']) . "<br>";
        }

        if (!empty($result['errors'])) {
            echo "错误: " . implode(', ', $result['errors']) . "<br>";
        }
    }
    echo "<hr>";
}

// 输出类似:
// 测试: 格式 'Y-m-d', 字符串 '2023-12-25'
// ✓ 解析成功
// 日期: 2023-12-25 00:00:00
// 验证: 通过
//
// 测试: 格式 'Y-m-d', 字符串 '2023-13-45'
// ✗ 解析失败
// 错误: The parsed date was invalid
//
// 测试: 格式 'd/m/Y', 字符串 '25/12/2023'
// ✓ 解析成功
// 日期: 2023-12-25 00:00:00
// 验证: 通过
?>

示例 5:时区处理

<?php
// 时区处理
$timezone_ny = new DateTimeZone('America/New_York');
$timezone_tokyo = new DateTimeZone('Asia/Tokyo');

// 从带时区信息的字符串创建
$date1 = date_create_immutable_from_format('Y-m-d H:i:s P', '2023-12-25 14:30:00 +09:00');
echo "带时区偏移: " . ($date1 !== false ? $date1->format('Y-m-d H:i:s T P') : '失败') . "<br>";

// 指定时区创建
$date2 = date_create_immutable_from_format('Y-m-d H:i:s', '2023-12-25 14:30:00', $timezone_ny);
echo "纽约时区: " . ($date2 !== false ? $date2->format('Y-m-d H:i:s T') : '失败') . "<br>";

$date3 = date_create_immutable_from_format('Y-m-d H:i:s', '2023-12-25 14:30:00', $timezone_tokyo);
echo "东京时区: " . ($date3 !== false ? $date3->format('Y-m-d H:i:s T') : '失败') . "<br><br>";

// 比较时间戳
if ($date2 !== false && $date3 !== false) {
    echo "时间戳比较:<br>";
    echo "纽约时间戳: " . $date2->getTimestamp() . "<br>";
    echo "东京时间戳: " . $date3->getTimestamp() . "<br>";
    echo "时差: " . (($date3->getOffset() - $date2->getOffset()) / 3600) . " 小时<br>";
}

// 输出类似:
// 带时区偏移: 2023-12-25 14:30:00 +09:00 +09:00
// 纽约时区: 2023-12-25 14:30:00 EST
// 东京时区: 2023-12-25 14:30:00 JST
//
// 时间戳比较:
// 纽约时间戳: 1703529000
// 东京时间戳: 1703529000
// 时差: 14 小时
?>

示例 6:与 date_create_from_format() 比较

<?php
// 与可变版本比较
echo "<h4>date_create_immutable_from_format() 与 date_create_from_format() 比较:</h4>";

// 创建可变对象
$mutable = date_create_from_format('Y-m-d H:i:s', '2023-12-25 14:30:00');
echo "可变对象类型: " . get_class($mutable) . "<br>";

// 创建不可变对象
$immutable = date_create_immutable_from_format('Y-m-d H:i:s', '2023-12-25 14:30:00');
echo "不可变对象类型: " . get_class($immutable) . "<br><br>";

// 测试修改操作
echo "<h5>修改操作对比:</h5>";

if ($mutable !== false && $immutable !== false) {
    // 修改可变对象
    $mutable_original = $mutable->format('Y-m-d H:i:s');
    $mutable_modified = $mutable->modify('+1 day');
    $mutable_after = $mutable->format('Y-m-d H:i:s');

    echo "可变对象 - 修改前: {$mutable_original}<br>";
    echo "可变对象 - 修改后: {$mutable_after}<br>";
    echo "可变对象 - 是否相同对象: " . ($mutable === $mutable_modified ? '是' : '否') . "<br><br>";

    // 修改不可变对象
    $immutable_original = $immutable->format('Y-m-d H:i:s');
    $immutable_modified = $immutable->modify('+1 day');
    $immutable_after = $immutable->format('Y-m-d H:i:s');

    echo "不可变对象 - 修改前: {$immutable_original}<br>";
    echo "不可变对象 - 原对象修改后: {$immutable_after}<br>";
    echo "不可变对象 - 新对象日期: " . $immutable_modified->format('Y-m-d H:i:s') . "<br>";
    echo "不可变对象 - 是否相同对象: " . ($immutable === $immutable_modified ? '是' : '否') . "<br>";
}

// 输出类似:
// date_create_immutable_from_format() 与 date_create_from_format() 比较:
// 可变对象类型: DateTime
// 不可变对象类型: DateTimeImmutable
//
// 修改操作对比:
// 可变对象 - 修改前: 2023-12-25 14:30:00
// 可变对象 - 修改后: 2023-12-26 14:30:00
// 可变对象 - 是否相同对象: 是
//
// 不可变对象 - 修改前: 2023-12-25 14:30:00
// 不可变对象 - 原对象修改后: 2023-12-25 14:30:00
// 不可变对象 - 新对象日期: 2023-12-26 14:30:00
// 不可变对象 - 是否相同对象: 否
?>

示例 7:处理各种日期格式的实用函数

<?php
// 处理各种日期格式的实用函数
class DateParser {
    // 常见日期格式
    private static $common_formats = [
        'Y-m-d H:i:s',
        'Y-m-d',
        'd/m/Y H:i:s',
        'd/m/Y',
        'm/d/Y H:i:s',
        'm/d/Y',
        'Y年m月d日 H:i:s',
        'Y年m月d日',
        'Ymd His',
        'Ymd',
        'Y-m-d\TH:i:sP', // ISO 8601
        'D, d M Y H:i:s O' // RFC 2822
    ];

    public static function parseFlexible($date_str) {
        // 首先尝试常见格式
        foreach (self::$common_formats as $format) {
            $date = date_create_immutable_from_format($format, $date_str);
            if ($date !== false) {
                $errors = DateTimeImmutable::getLastErrors();
                if (empty($errors['warnings']) && empty($errors['errors'])) {
                    return [
                        'success' => true,
                        'date' => $date,
                        'format' => $format,
                        'method' => 'createFromFormat'
                    ];
                }
            }
        }

        // 如果常见格式都失败,尝试 strtotime
        $timestamp = strtotime($date_str);
        if ($timestamp !== false) {
            $date = date_create_immutable('@' . $timestamp);
            return [
                'success' => true,
                'date' => $date,
                'format' => null,
                'method' => 'strtotime'
            ];
        }

        return [
            'success' => false,
            'error' => '无法解析日期字符串'
        ];
    }

    public static function detectFormat($date_str) {
        foreach (self::$common_formats as $format) {
            $date = date_create_immutable_from_format($format, $date_str);
            if ($date !== false) {
                $errors = DateTimeImmutable::getLastErrors();
                if (empty($errors['warnings']) && empty($errors['errors'])) {
                    // 验证解析结果
                    $formatted = $date->format($format);
                    if ($formatted === $date_str) {
                        return $format;
                    }
                }
            }
        }
        return false;
    }
}

// 测试
$test_dates = [
    '2023-12-25 14:30:00',
    '25/12/2023',
    '12/25/2023 14:30:00',
    '2023年12月25日',
    '20231225 143000',
    '2023-12-25T14:30:00+08:00',
    'Mon, 25 Dec 2023 14:30:00 +0800',
    'next Monday',
    'invalid date'
];

echo "<h4>灵活日期解析:</h4>";
foreach ($test_dates as $date_str) {
    $result = DateParser::parseFlexible($date_str);

    echo "<strong>输入:</strong> '{$date_str}'<br>";
    if ($result['success']) {
        echo "<span style='color: green;'>✓ 解析成功</span><br>";
        echo "日期: " . $result['date']->format('Y-m-d H:i:s') . "<br>";
        echo "方法: " . $result['method'] . "<br>";
        if ($result['format']) {
            echo "格式: " . $result['format'] . "<br>";
        }
    } else {
        echo "<span style='color: red;'>✗ 解析失败</span><br>";
    }
    echo "<hr>";
}

// 检测格式
echo "<h4>日期格式检测:</h4>";
$sample = '2023-12-25 14:30:00';
$format = DateParser::detectFormat($sample);
echo "示例: '{$sample}'<br>";
echo "检测到的格式: " . ($format ?: '未识别') . "<br>";

// 输出类似:
// 灵活日期解析:
// 输入: '2023-12-25 14:30:00'
// ✓ 解析成功
// 日期: 2023-12-25 14:30:00
// 方法: createFromFormat
// 格式: Y-m-d H:i:s
//
// 输入: '25/12/2023'
// ✓ 解析成功
// 日期: 2023-12-25 00:00:00
// 方法: createFromFormat
// 格式: d/m/Y
//
// 输入: 'next Monday'
// ✓ 解析成功
// 日期: 2023-08-21 00:00:00
// 方法: strtotime
//
// 输入: 'invalid date'
// ✗ 解析失败
?>

示例 8:从时间戳创建

<?php
// 从时间戳创建不可变对象
echo "<h4>从 Unix 时间戳创建:</h4>";

$timestamp = 1703500200; // 2023-12-25 14:30:00 UTC

// 方法1: 使用 U 格式
$date1 = date_create_immutable_from_format('U', $timestamp);
echo "方法1 (U格式): " . ($date1 !== false ? $date1->format('Y-m-d H:i:s') : '失败') . "<br>";

// 方法2: 在时间戳前加 @
$date2 = date_create_immutable_from_format('U', (string)$timestamp);
echo "方法2 (字符串): " . ($date2 !== false ? $date2->format('Y-m-d H:i:s') : '失败') . "<br>";

// 方法3: 使用 date_create_immutable
$date3 = date_create_immutable('@' . $timestamp);
echo "方法3 (@前缀): " . $date3->format('Y-m-d H:i:s') . "<br><br>";

// 带时区的时间戳
echo "<h4>带时区的时间戳:</h4>";
$timezone = new DateTimeZone('America/New_York');
$date4 = date_create_immutable_from_format('U', $timestamp, $timezone);
echo "纽约时区: " . ($date4 !== false ? $date4->format('Y-m-d H:i:s T') : '失败') . "<br>";

// 验证
if ($date1 !== false && $date3 !== false) {
    echo "<br><strong>验证:</strong><br>";
    echo "date_create_immutable_from_format: " . $date1->getTimestamp() . "<br>";
    echo "date_create_immutable: " . $date3->getTimestamp() . "<br>";
    echo "是否相同: " . ($date1->getTimestamp() === $date3->getTimestamp() ? '是' : '否') . "<br>";
}

// 输出类似:
// 从 Unix 时间戳创建:
// 方法1 (U格式): 2023-12-25 14:30:00
// 方法2 (字符串): 2023-12-25 14:30:00
// 方法3 (@前缀): 2023-12-25 14:30:00
//
// 带时区的时间戳:
// 纽约时区: 2023-12-25 09:30:00 EST
//
// 验证:
// date_create_immutable_from_format: 1703500200
// date_create_immutable: 1703500200
// 是否相同: 是
?>

格式字符参考

字符 描述 示例
d 月份中的第几天,有前导零的 2 位数字 01 到 31
D 星期中的第几天,文本表示,3 个字母 Mon 到 Sun
j 月份中的第几天,没有前导零 1 到 31
l 星期几,完整的文本格式 Sunday 到 Saturday
F 月份,完整的文本格式 January 到 December
m 数字表示的月份,有前导零 01 到 12
M 三个字母缩写表示的月份 Jan 到 Dec
n 数字表示的月份,没有前导零 1 到 12
Y 4 位数字完整表示的年份 例如:1999 或 2003
y 2 位数字表示的年份 例如:99 或 03
a 小写的上午和下午值 am 或 pm
A 大写的上午和下午值 AM 或 PM
g 小时,12 小时格式,没有前导零 1 到 12
G 小时,24 小时格式,没有前导零 0 到 23
h 小时,12 小时格式,有前导零 01 到 12
H 小时,24 小时格式,有前导零 00 到 23
i 有前导零的分钟数 00 到 59
s 有前导零的秒数 00 到 59
u 微秒 例如:654321
v 毫秒 例如:654
O 与格林威治时间相差的小时数 例如:+0200
P 与格林威治时间(GMT)的差别,小时和分钟之间有冒号分隔 例如:+02:00
T 时区缩写 例如:EST, MDT ...
U 从 Unix 纪元(January 1 1970 00:00:00 GMT)开始至今的秒数 参见 time()

注意事项

  • date_create_immutable_from_format() 对格式要求严格,日期字符串必须完全匹配格式
  • strtotime() 不同,这个函数不会尝试猜测或自动修正格式
  • 返回的是不可变对象,所有修改操作都返回新对象
  • 如果解析失败,函数返回 false,可以使用 DateTimeImmutable::getLastErrors() 获取错误信息
  • 格式字符串中的字符可能需要转义,比如使用 \T 表示字面量 'T'
  • 在 PHP 7.1+ 中,可以使用 ! 前缀来重置所有字段到 Unix 纪元

最佳实践

场景 建议
已知确切格式 使用 date_create_immutable_from_format() 确保精确解析
用户输入日期 先验证格式,再使用此函数解析
多格式支持 尝试多种格式,直到成功解析
错误处理 总是检查返回值,使用 getLastErrors() 获取详细错误
性能敏感 此函数比 strtotime() 更快,因为不需要猜测格式
时区处理 明确指定时区,避免依赖系统默认时区

相关函数