PHP date_create_immutable()函数

date_create_immutable() 函数创建一个新的不可变 DateTime 对象,返回 DateTimeImmutable 对象。与普通的 DateTime 对象不同,DateTimeImmutable 对象是不可变的,任何对它的修改都会返回一个新的对象,而原对象保持不变。

这个函数是 new DateTimeImmutable() 构造函数的过程化别名,推荐在函数式编程中使用。

语法

date_create_immutable([string $datetime = "now" [, DateTimeZone $timezone = null]]) : DateTimeImmutable|false

参数说明

参数 默认值 描述
datetime "now"

可选。 日期/时间字符串,任何能被 strtotime() 解析的格式。

例如:"2023-12-25 14:30:00", "next Monday", "+1 week" 等。

timezone null

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

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

返回值

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

示例

示例 1:基本用法

<?php
// 创建当前时间的不可变对象
$date = date_create_immutable();

if ($date !== false) {
    echo "当前时间: " . $date->format('Y-m-d H:i:s') . "<br>";
    echo "时区: " . $date->getTimezone()->getName() . "<br>";
    echo "对象类型: " . get_class($date);
}

// 输出类似:
// 当前时间: 2023-08-15 10:30:45
// 时区: Asia/Shanghai
// 对象类型: DateTimeImmutable
?>

示例 2:创建指定时间的不可变对象

<?php
// 创建指定时间的不可变对象
$date1 = date_create_immutable('2023-12-25 14:30:00');
$date2 = date_create_immutable('next Monday');
$date3 = date_create_immutable('+1 week 2 days');

echo "指定时间: " . $date1->format('Y-m-d H:i:s') . "<br>";
echo "下周一: " . $date2->format('Y-m-d') . "<br>";
echo "1周2天后: " . $date3->format('Y-m-d') . "<br>";

// 使用相对时间
$date4 = date_create_immutable('first day of next month');
echo "下个月第一天: " . $date4->format('Y-m-d') . "<br>";

// 输出类似:
// 指定时间: 2023-12-25 14:30:00
// 下周一: 2023-08-21
// 1周2天后: 2023-08-24
// 下个月第一天: 2023-09-01
?>

示例 3:使用时区

<?php
// 使用时区创建不可变对象
$timezone_ny = new DateTimeZone('America/New_York');
$timezone_tokyo = new DateTimeZone('Asia/Tokyo');

$date_ny = date_create_immutable('2023-12-25 12:00:00', $timezone_ny);
$date_tokyo = date_create_immutable('2023-12-25 12:00:00', $timezone_tokyo);

echo "纽约时间: " . $date_ny->format('Y-m-d H:i:s T') . "<br>";
echo "东京时间: " . $date_tokyo->format('Y-m-d H:i:s T') . "<br><br>";

// 比较时间戳(应该相同,因为指定了不同的时区)
echo "时间戳比较:<br>";
echo "纽约时间戳: " . $date_ny->getTimestamp() . "<br>";
echo "东京时间戳: " . $date_tokyo->getTimestamp() . "<br>";
echo "是否相同: " . ($date_ny->getTimestamp() === $date_tokyo->getTimestamp() ? '是' : '否');

// 输出类似:
// 纽约时间: 2023-12-25 12:00:00 EST
// 东京时间: 2023-12-25 12:00:00 JST
//
// 时间戳比较:
// 纽约时间戳: 1703520000
// 东京时间戳: 1703520000
// 是否相同: 是
?>

示例 4:不可变性演示(与可变对象对比)

<?php
// 不可变对象与可变对象的对比
echo "<h4>可变 DateTime 对象:</h4>";
$mutable_date = date_create('2023-01-01');
echo "原始日期: " . $mutable_date->format('Y-m-d') . "<br>";

// 修改可变对象
$modified_mutable = $mutable_date->modify('+1 month');
echo "修改后日期: " . $mutable_date->format('Y-m-d') . "<br>";
echo "返回的新对象: " . ($modified_mutable === $mutable_date ? '是同一个对象' : '新对象') . "<br><br>";

echo "<h4>不可变 DateTimeImmutable 对象:</h4>";
$immutable_date = date_create_immutable('2023-01-01');
echo "原始日期: " . $immutable_date->format('Y-m-d') . "<br>";

// 尝试修改不可变对象
$modified_immutable = $immutable_date->modify('+1 month');
echo "原对象日期: " . $immutable_date->format('Y-m-d') . "<br>";
echo "修改后返回的新对象日期: " . $modified_immutable->format('Y-m-d') . "<br>";
echo "原对象与新对象是否相同: " . ($immutable_date === $modified_immutable ? '是' : '否') . "<br>";

// 输出类似:
// 可变 DateTime 对象:
// 原始日期: 2023-01-01
// 修改后日期: 2023-02-01
// 返回的新对象: 是同一个对象
//
// 不可变 DateTimeImmutable 对象:
// 原始日期: 2023-01-01
// 原对象日期: 2023-01-01
// 修改后返回的新对象日期: 2023-02-01
// 原对象与新对象是否相同: 否
?>

示例 5:日期运算和比较

<?php
// 日期运算和比较
$start_date = date_create_immutable('2023-01-01');

// 创建新的不可变对象进行运算
$one_month_later = $start_date->modify('+1 month');
$two_months_later = $one_month_later->modify('+1 month');
$one_year_later = $start_date->modify('+1 year');

echo "开始日期: " . $start_date->format('Y-m-d') . "<br>";
echo "一个月后: " . $one_month_later->format('Y-m-d') . "<br>";
echo "两个月后: " . $two_months_later->format('Y-m-d') . "<br>";
echo "一年后: " . $one_year_later->format('Y-m-d') . "<br><br>";

// 日期比较
echo "日期比较:<br>";
echo "开始日期 < 一个月后: " . ($start_date < $one_month_later ? '是' : '否') . "<br>";
echo "一个月后 < 两个月后: " . ($one_month_later < $two_months_later ? '是' : '否') . "<br>";
echo "一年后 > 开始日期: " . ($one_year_later > $start_date ? '是' : '否') . "<br><br>";

// 日期差异
$diff = $start_date->diff($one_year_later);
echo "日期差异: " . $diff->format('%y 年 %m 月 %d 天') . "<br>";

// 输出类似:
// 开始日期: 2023-01-01
// 一个月后: 2023-02-01
// 两个月后: 2023-03-01
// 一年后: 2024-01-01
//
// 日期比较:
// 开始日期 < 一个月后: 是
// 一个月后 < 两个月后: 是
// 一年后 > 开始日期: 是
//
// 日期差异: 1 年 0 月 0 天
?>

示例 6:错误处理

<?php
// 错误处理
function safeDateCreateImmutable($datetime, $timezone = null) {
    $date = date_create_immutable($datetime, $timezone);

    if ($date === false) {
        return "错误: 无法解析日期时间字符串 '{$datetime}'";
    }

    return $date;
}

// 测试各种情况
$test_cases = [
    '2023-12-25 14:30:00',
    'invalid date string',
    'next Monday',
    '2023-13-45', // 无效日期
    '',
    '2023-02-28 +1 day' // 有效
];

foreach ($test_cases as $datetime) {
    $result = safeDateCreateImmutable($datetime);

    if (is_string($result)) {
        echo "'{$datetime}': {$result}<br>";
    } else {
        echo "'{$datetime}': 成功创建 - " . $result->format('Y-m-d H:i:s') . "<br>";
    }
}

// 输出类似:
// '2023-12-25 14:30:00': 成功创建 - 2023-12-25 14:30:00
// 'invalid date string': 错误: 无法解析日期时间字符串 'invalid date string'
// 'next Monday': 成功创建 - 2023-08-21 00:00:00
// '2023-13-45': 错误: 无法解析日期时间字符串 '2023-13-45'
// '': 成功创建 - 2023-08-15 10:30:45
// '2023-02-28 +1 day': 成功创建 - 2023-03-01 00:00:00
?>

示例 7:与 date_create() 的比较

<?php
// 比较 date_create_immutable() 和 date_create()
echo "<h4>date_create_immutable() 与 date_create() 比较:</h4>";

// 创建可变对象
$mutable = date_create('2023-01-01');
echo "可变对象类型: " . get_class($mutable) . "<br>";

// 创建不可变对象
$immutable = date_create_immutable('2023-01-01');
echo "不可变对象类型: " . get_class($immutable) . "<br><br>";

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

// 修改可变对象
$mutable_original = $mutable->format('Y-m-d');
$mutable_modified = $mutable->modify('+1 day');
$mutable_after = $mutable->format('Y-m-d');

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

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

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

// 序列化测试
echo "<h5>序列化对比:</h5>";
$serialized_mutable = serialize($mutable);
$serialized_immutable = serialize($immutable);

echo "可变对象序列化长度: " . strlen($serialized_mutable) . "<br>";
echo "不可变对象序列化长度: " . strlen($serialized_immutable) . "<br>";

// 输出类似:
// date_create_immutable() 与 date_create() 比较:
// 可变对象类型: DateTime
// 不可变对象类型: DateTimeImmutable
//
// 修改操作对比:
// 可变对象 - 修改前: 2023-01-01
// 可变对象 - 修改后: 2023-01-02
// 可变对象 - 是否相同对象: 是
//
// 不可变对象 - 修改前: 2023-01-01
// 不可变对象 - 原对象修改后: 2023-01-01
// 不可变对象 - 新对象日期: 2023-01-02
// 不可变对象 - 是否相同对象: 否
//
// 序列化对比:
// 可变对象序列化长度: 79
// 不可变对象序列化长度: 90
?>

示例 8:在实际应用中使用不可变对象

<?php
// 在实际应用中使用不可变对象
class EventScheduler {
    private $events = [];

    public function addEvent($name, $start_date) {
        // 确保使用不可变对象
        if (!$start_date instanceof DateTimeImmutable) {
            $start_date = date_create_immutable($start_date->format('Y-m-d H:i:s'), $start_date->getTimezone());
        }

        $this->events[] = [
            'name' => $name,
            'start' => $start_date,
            'end' => $start_date->modify('+2 hours') // 返回新对象,不影响原对象
        ];

        return $this;
    }

    public function getEventsAfter($date) {
        $result = [];
        foreach ($this->events as $event) {
            if ($event['start'] > $date) {
                $result[] = $event;
            }
        }
        return $result;
    }

    public function displayEvents() {
        foreach ($this->events as $event) {
            echo "活动: " . $event['name'] . "<br>";
            echo "开始: " . $event['start']->format('Y-m-d H:i:s') . "<br>";
            echo "结束: " . $event['end']->format('Y-m-d H:i:s') . "<br><br>";
        }
    }
}

// 使用示例
$scheduler = new EventScheduler();

// 添加活动
$scheduler->addEvent('会议', date_create_immutable('2023-12-25 09:00:00'))
         ->addEvent('培训', date_create_immutable('2023-12-26 14:00:00'))
         ->addEvent('派对', date_create_immutable('2023-12-31 20:00:00'));

echo "<h4>所有活动:</h4>";
$scheduler->displayEvents();

// 获取特定日期之后的活动
$cutoff_date = date_create_immutable('2023-12-26 00:00:00');
$future_events = $scheduler->getEventsAfter($cutoff_date);

echo "<h4>2023-12-26 之后的活动:</h4>";
if (empty($future_events)) {
    echo "没有找到活动";
} else {
    foreach ($future_events as $event) {
        echo "活动: " . $event['name'] . " (" . $event['start']->format('Y-m-d') . ")<br>";
    }
}

// 输出类似:
// 所有活动:
// 活动: 会议
// 开始: 2023-12-25 09:00:00
// 结束: 2023-12-25 11:00:00
//
// 活动: 培训
// 开始: 2023-12-26 14:00:00
// 结束: 2023-12-26 16:00:00
//
// 活动: 派对
// 开始: 2023-12-31 20:00:00
// 结束: 2024-01-01 22:00:00
//
// 2023-12-26 之后的活动:
// 活动: 派对 (2023-12-31)
?>

DateTime 与 DateTimeImmutable 对比

特性 DateTime(可变) DateTimeImmutable(不可变)
创建函数 date_create() 或 new DateTime() date_create_immutable() 或 new DateTimeImmutable()
修改行为 修改原对象,返回自身($this) 返回新对象,原对象不变
线程安全 不安全(可能被意外修改) 安全(不可修改)
函数式编程 不适用 适用
内存使用 较低(原地修改) 较高(创建新对象)
调试难度 较难(值可能变化) 较易(值不变)
推荐场景 简单脚本,性能敏感场景 复杂应用,多线程,函数式编程

常用方法

方法 描述 是否返回新对象
modify() 修改日期时间 是
add() 添加时间间隔 是
sub() 减去时间间隔 是
setDate() 设置日期 是
setTime() 设置时间 是
setTimestamp() 设置时间戳 是
setTimezone() 设置时区 是
format() 格式化日期时间 否(返回字符串)
getTimestamp() 获取时间戳 否(返回整数)
getTimezone() 获取时区 否(返回DateTimeZone)
diff() 计算时间差 否(返回DateInterval)

注意事项

  • DateTimeImmutable 对象不可变,所有修改操作都返回新对象
  • 不可变对象在函数式编程和多线程环境中更安全
  • 如果不需要修改原始日期,推荐使用不可变对象
  • 不可变对象会占用更多内存,因为每次修改都创建新对象
  • DateTime 和 DateTimeImmutable 可以相互转换
  • 在 PHP 7.2+ 中,date_create_immutable() 总是可用

最佳实践

场景 建议
需要保留原始日期 使用 DateTimeImmutable
函数式编程 使用 DateTimeImmutable
性能敏感场景 使用 DateTime(如果不需要不可变性)
API 设计 接受和返回 DateTimeImmutable
数据库操作 使用 DateTimeImmutable 避免意外修改
缓存日期对象 使用 DateTimeImmutable 保证值不变

相关函数

  • strtotime() - 将英文文本日期时间解析为 Unix 时间戳
  • date() - 格式化本地时间/日期
  • time() - 返回当前的 Unix 时间戳