Skip to content

DateTime 类

PHP 的 DateTime 类是处理日期和时间的核心面向对象接口。它提供了创建、修改、比较和格式化日期时间的完整 API,比传统的 date()strtotime() 函数更加灵活和强大。PHP 8.2+ 对 DateTimeImmutable 做了多项改进。

前置知识

阅读本节前,建议先了解:PHP 面向对象编程字符串处理

基础概念

DateTime vs DateTimeImmutable

php
<?php
declare(strict_types=1);

// DateTime - 可变对象(修改时改变自身)
$dt = new DateTime('2024-01-15');
$dt->modify('+1 day');
echo $dt->format('Y-m-d') . PHP_EOL; // 2024-01-16

// DateTimeImmutable - 不可变对象(修改时返回新对象)
$dti = new DateTimeImmutable('2024-01-15');
$newDt = $dti->modify('+1 day');
echo $dti->format('Y-m-d') . PHP_EOL;   // 2024-01-15(不变)
echo $newDt->format('Y-m-d') . PHP_EOL;  // 2024-01-16

// PHP 8.2+ 推荐使用 DateTimeImmutable
// 因为不可变对象更安全,避免意外的副作用

创建 DateTime

构造方式

php
<?php
declare(strict_types=1);

// 1. 当前时间
$now = new DateTime();
$nowImmutable = new DateTimeImmutable();

// 2. 从字符串创建
$dt = new DateTime('2024-01-15 14:30:00');
$dt = new DateTime('2024-01-15');
$dt = new DateTime('tomorrow');
$dt = new DateTime('next monday');
$dt = new DateTime('last day of next month');

// 3. 从时间戳创建
$dt = DateTime::createFromFormat('U', (string) time());
$dt = new DateTime('@' . time()); // @ 前缀表示 Unix 时间戳

// 4. 从指定格式创建
$dt = DateTime::createFromFormat('Y-m-d H:i:s', '2024-01-15 14:30:00');

// 5. createFromImmutable / createFromMutable
$immutable = new DateTimeImmutable('2024-01-15');
$mutable = DateTime::createFromMutable($immutable->toMutable());

$mutable = new DateTime('2024-01-15');
$immutable = DateTimeImmutable::createFromMutable($mutable);

createFromFormat 详细用法

php
<?php
declare(strict_types=1);

// 解析各种格式的日期字符串
$formats = [
    ['d/m/Y', '15/01/2024'],        // 15/01/2024
    ['Y年m月d日', '2024年1月15日'],   // 中文日期
    ['Ymd', '20240115'],             // 紧凑格式
    ['d M Y', '15 Jan 2024'],        // 英文缩写
];

foreach ($formats as [$format, $string]) {
    $dt = DateTime::createFromFormat($format, $string);
    if ($dt !== false) {
        echo "{$string} => " . $dt->format('Y-m-d') . PHP_EOL;
    }
}

// createFromFormat 时区参数
$dt = DateTime::createFromFormat(
    'Y-m-d H:i:s',
    '2024-01-15 14:30:00',
    new DateTimeZone('Asia/Shanghai')
);

// 解析时获取错误信息
$date = DateTime::createFromFormat('Y-m-d', '2024-13-45');
if ($date === false) {
    $errors = DateTime::getLastErrors();
    print_r($errors);
}

格式化输出

format() 方法

php
<?php
declare(strict_types=1);

$dt = new DateTime('2024-01-15 14:30:45');

echo $dt->format('Y-m-d H:i:s') . PHP_EOL;       // 2024-01-15 14:30:45
echo $dt->format('Y/m/d') . PHP_EOL;               // 2024/01/15
echo $dt->format('d.m.Y') . PHP_EOL;               // 15.01.2024
echo $dt->format('F j, Y') . PHP_EOL;              // January 15, 2024
echo $dt->format('c') . PHP_EOL;                    // ISO 8601: 2024-01-15T14:30:45+00:00
echo $dt->format('r') . PHP_EOL;                    // RFC 2822: Mon, 15 Jan 2024 14:30:45 +0000
echo $dt->format('U') . PHP_EOL;                    // Unix 时间戳
echo $dt->format('\\\\日是第 W 周') . PHP_EOL;    // 今日是第 03 周

// 常用格式字符:
// Y - 4位年份  m - 2位月份  d - 2位日期
// H - 24小时制  i - 分钟  s - 秒
// w - 周几(0-6)  W - 年中第几周
// z - 年中第几天  t - 月份天数
// L - 是否闰年

修改日期

modify() 方法

php
<?php
declare(strict_types=1);

$dt = new DateTime('2024-01-15');

// 相对时间修改
echo $dt->modify('+1 day')->format('Y-m-d') . PHP_EOL;     // 2024-01-16
echo $dt->modify('-1 week')->format('Y-m-d') . PHP_EOL;     // 2024-01-08
echo $dt->modify('+2 months')->format('Y-m-d') . PHP_EOL;  // 2024-03-15
echo $dt->modify('+1 year')->format('Y-m-d') . PHP_EOL;     // 2025-01-15
echo $dt->modify('next monday')->format('Y-m-d') . PHP_EOL;
echo $dt->modify('last day of this month')->format('Y-m-d') . PHP_EOL;
echo $dt->modify('first day of next month')->format('Y-m-d') . PHP_EOL;

// 注意:DateTime 的 modify 修改自身
// DateTimeImmutable 返回新对象

add() / sub() 方法

php
<?php
declare(strict_types=1);

$dt = new DateTimeImmutable('2024-01-15');

// 使用 DateInterval
$interval = new DateInterval('P1Y2M3D'); // 1年2月3天
echo $dt->add($interval)->format('Y-m-d') . PHP_EOL; // 2025-03-18

$interval = DateInterval::createFromDateString('-6 months');
echo $dt->add($interval)->format('Y-m-d') . PHP_EOL;

// sub() 减去间隔
$dt2 = $dt->sub(new DateInterval('P10D'));
echo $dt2->format('Y-m-d') . PHP_EOL; // 2024-01-05

// setTimestamp
$dt3 = $dt->setTimestamp(1700000000);
echo $dt3->format('Y-m-d H:i:s') . PHP_EOL;

比较日期

php
<?php
declare(strict_types=1);

$dt1 = new DateTime('2024-01-15');
$dt2 = new DateTime('2024-03-20');

// diff() - 计算两个日期的差值
$diff = $dt1->diff($dt2);
echo "差异: {$diff->y}年 {$diff->m}月 {$diff->d}天" . PHP_EOL;
echo "总天数: " . $diff->days . PHP_EOL;
echo "是否反转: " . ($diff->invert ? '是' : '否') . PHP_EOL;

// compare() - 比较两个日期
// PHP 8.2+ DateTimeImmutable::compare
$cmp = DateTimeImmutable::compare(
    new DateTimeImmutable('2024-01-15'),
    new DateTimeImmutable('2024-03-20')
);
echo "比较结果: {$cmp}" . PHP_EOL; // -1 (第一个小于第二个)

// 手动比较
if ($dt1 < $dt2) {
    echo "dt1 在 dt2 之前" . PHP_EOL;
}
if ($dt1 > $dt2) {
    echo "dt1 在 dt2 之后" . PHP_EOL;
}
if ($dt1 == $dt2) {
    echo "dt1 和 dt2 相同" . PHP_EOL;
}

实战示例

日期工具类

php
<?php
declare(strict_types=1);

class DateHelper
{
    /**
     * 判断是否为工作日
     */
    public static function isWeekday(DateTimeInterface $dt): bool
    {
        return (int) $dt->format('N') <= 5;
    }

    /**
     * 获取两个日期之间的工作日数
     */
    public static function weekdaysBetween(
        DateTimeInterface $start,
        DateTimeInterface $end
    ): int {
        $count = 0;
        $period = new DatePeriod($start, new DateInterval('P1D'), $end);

        foreach ($period as $day) {
            if (self::isWeekday($day)) {
                $count++;
            }
        }

        return $count;
    }

    /**
     * 获取某月的天数
     */
    public static function daysInMonth(int $year, int $month): int
    {
        return (int) (new DateTimeImmutable("{$year}-{$month}-01"))
            ->format('t');
    }

    /**
     * 获取年龄
     */
    public static function calculateAge(DateTimeInterface $birthday): int
    {
        return $birthday->diff(new DateTimeImmutable())->y;
    }
}

注意事项

  • PHP 8.2+ 推荐使用 DateTimeImmutable
  • DateTime::compare() (PHP 8.2+) 用于安全的日期比较
  • modify() 中使用 next/last/this 关键字可以方便地操作日期
  • 时区处理见 DateTimeZone

下一节

继续学习:DateInterval

参考链接