PHP date() 怎么用:日期格式、常见参数与示例

PHP date() 怎么用:日期格式、常见参数与示例

PHP date() 怎么用:日期格式、常见参数与示例

PHP 的 date() 用于把一个时间点格式化为字符串。它适合生成页面上的日期、时间、文件名中的时间标记,以及采用固定格式的日志内容。它本身不负责解析用户输入、不提供本地化翻译,也不会自动验证某个日期字符串是否真实存在。

掌握 date(),重点是分清格式字符串里的特殊字符、时间戳的单位和时区的来源。本文从基本用法讲起,再说明周数、闰年、微秒等容易出错的边界,并给出适用于常见场景的代码。

一、基本语法与参数

date() 的基本形式如下:

<?php
date(string $format, ?int $timestamp = null): string
?>
  • $format:必填的格式字符串,由格式字符和普通文本组成。
  • $timestamp:可选的 Unix 时间戳,以秒为单位。省略或传入 null 时,使用当前时间。
  • 返回值:格式化后的字符串。

下面的代码可以直接运行:

<?php
date_default_timezone_set('Asia/Shanghai');

echo date('Y-m-d H:i:s');
// 例如:2025-03-08 14:30:00,具体时间取决于运行时的当前时刻

$timestamp = 1700000000;
echo date('Y-m-d H:i:s', $timestamp);
// 2023-11-15 06:13:20
?>

时间戳表示从 Unix 纪元时刻 1970-01-01 00:00:00 UTC 起经过的秒数。它不是毫秒,也不是一个可以直接阅读的日期字符串。若拿到的是毫秒时间戳,应先换算为秒;否则输出的日期会远超预期。

<?php
$milliseconds = 1700000000000;

// 先换算为秒。若原始数据可能包含小数,可按业务要求保留或取整。
$seconds = (int) ($milliseconds / 1000);

echo date('Y-m-d H:i:s', $seconds);
?>

不要把日期字符串直接当作 $timestamp 传入。需要解析字符串时,应使用 DateTimeImmutable::createFromFormat() 或其他明确的解析方法,并检查解析结果及错误信息。

二、最常用的日期格式字符

date() 的格式规则由单个字符表示。未转义的字母可能有特殊含义,因此不能随意把格式字符串当作普通文本拼接。

字符含义示例
Y四位年份2025
y两位年份25
m两位月份,范围 01 至 1203
n不补零的月份3
d两位月份中的日期08
j不补零的日期8
H24 小时制小时,补零09、21
G24 小时制小时,不补零9、21
h12 小时制小时,补零09
g12 小时制小时,不补零9
i分钟,补零05
s秒,补零07
a / A小写或大写的上午、下午标记pm / PM

例如,常见的年月日和 24 小时制时间格式是 Y-m-d H:i:s。格式中的连字符、空格和冒号会原样输出:

<?php
date_default_timezone_set('Asia/Shanghai');

echo date('Y-m-d H:i:s');
// 例如:2025-03-08 14:05:07

echo date('Y年n月j日 G:i');
// 例如:2025年3月8日 14:5
?>

i 是分钟,m 是月份;这两个字符最容易写混。写成 H:m:s 会把月份放到分钟的位置,不会得到预期结果。

三、星期、月份长度与闰年

星期和周数有多套定义,使用时应先确定业务规则:

  • D:英文星期缩写,如 Mon。
  • l(小写字母 L):英文星期全称,如 Monday。
  • N:ISO-8601 星期数字,星期一为 1,星期日为 7。
  • w:星期数字,星期日为 0,星期六为 6。
  • W:ISO-8601 周数,补零的两位数。
  • o:ISO 周数对应的周所属年份,可能与日历年份 Y 不同。

如果系统需要按 ISO 周展示“周次”,应该把 o 和 W 配套使用,而不是简单地把 Y 与 W 拼在一起。年初、年末的日期可能属于相邻的 ISO 周年。

<?php
date_default_timezone_set('UTC');

$timestamp = strtotime('2021-01-01 12:00:00 UTC');

echo date('Y-m-d', $timestamp);
// 2021-01-01

echo date('o-\WW', $timestamp);
// 2020-W53
?>

t 返回当前月份的天数,L 返回当前年份是否为闰年:闰年为 1,平年为 0。

<?php
date_default_timezone_set('UTC');

$timestamp = strtotime('2024-02-10 12:00:00 UTC');

echo date('Y-m-t', $timestamp);
// 2024-02-29

echo date('L', $timestamp);
// 1,表示闰年
?>

需要生成某个月的末日时,可以用 t,不必自行写“二月有几天”的判断。但如果程序要处理严格的日历业务,仍应使用清晰的日期对象和业务规则,避免通过字符串拼接或模糊解析来推算日期。

四、时区:不要依赖服务器默认值

date() 根据 PHP 当前配置的默认时区解释并格式化时间。若没有显式设置,PHP 会使用配置中的 date.timezone;不同服务器、容器或开发环境的配置可能不同。同一时间戳因此可能输出不同的本地日期和时间。

在应用入口明确设置时区,可以让行为更可预测:

<?php
date_default_timezone_set('Asia/Shanghai');

echo date('Y-m-d H:i:s');
?>

也可以在配置文件中设置 date.timezone。生产应用通常应统一约定:数据库和系统内部时间按 UTC 保存或传递,展示时再转换到用户所处时区。使用 UTC 格式化时,可以这样写:

<?php
date_default_timezone_set('UTC');

echo date('Y-m-d\TH:i:sP');
// 例如:2025-03-08T06:30:00+00:00
?>

格式字符 e 可输出时区标识,例如 UTC 或 Asia/Shanghai;T 输出时区缩写,缩写可能含混,不宜单独用来标识时区;P 输出带冒号的 UTC 偏移量,如 +08:00;Z 输出以秒表示的 UTC 偏移量。

时区偏移并不总是固定的。采用夏令时的地区可能在一年中的不同日期使用不同偏移,因此不要把某个城市简单写死为固定的 +HH:MM。应使用有效的 IANA 时区名称,并在需要时让日期对象根据指定时区处理。

<?php
$zone = new DateTimeZone('America/New_York');

$time = new DateTimeImmutable('2025-07-01 12:00:00', $zone);

echo $time->format('Y-m-d H:i:s P e');
?>

注意,上例的时间字符串没有携带时区,因此会被解释为纽约当地时间。若输入本来就是一个绝对时间点,应在解析时提供明确的偏移或时区,避免把 UTC 时间误当成本地时间。

五、格式中的普通文本要转义

date() 会把格式字符串中的特定字母当作格式字符。想输出字母本身,需要在该字符前加反斜杠。例如,要生成 ISO 风格的时间字面量 T,可写作 \T:

<?php
date_default_timezone_set('UTC');

echo date('Y-m-d\TH:i:sP');
// 例如:2025-03-08T06:30:00+00:00
?>

PHP 字符串本身也有转义规则,因此要同时考虑 PHP 字符串和 date() 格式的转义。单引号字符串中,下面这种写法会让 date() 收到反斜杠和字母 T:

<?php
$format = 'Y-m-d\TH:i:s';
echo date($format);
?>

如果要输出较长的固定文本,逐个转义字母容易漏掉。可将不需要格式化的部分放在格式字符串之外,或者使用 DateTimeImmutable::format() 时同样遵循格式字符转义规则。对用户可配置的格式尤其要谨慎:格式字符可能改变输出含义,不能把它当作普通文本模板。

六、适合机器交换的格式

系统间交换日期时间时,尽量使用带时区信息、含义明确的格式,避免使用像 03/08/25 这样容易产生歧义的写法。PHP 提供了常用格式字符:

  • c:ISO 8601 风格的日期时间,包含偏移量。
  • r:RFC 2822 风格的日期时间。
  • U:Unix 时间戳,以十进制表示。
<?php
date_default_timezone_set('UTC');

$timestamp = 1700000000;

echo date('c', $timestamp);
// 2023-11-14T22:13:20+00:00

echo date('r', $timestamp);
// Tue, 14 Nov 2023 22:13:20 +0000

echo date('U', $timestamp);
// 1700000000
?>

需要符合某个接口约定时,应以该接口的规范为准,确认是否要求 UTC、是否要求毫秒、是否允许偏移量等。仅仅使用 c 并不代表一定满足所有接口的日期时间要求。

七、微秒与毫秒的边界

date() 接收的是以秒为单位的整数时间戳,不能通过它保留高精度的微秒时间。格式字符 u 在 date() 中会输出 000000;v 会输出 000。即使格式字符串写了这些字符,也不会让秒级时间戳获得真实的微秒或毫秒信息。

如果确实需要输出微秒,应使用支持微秒的时间对象,例如 DateTimeImmutable:

<?php
$time = DateTimeImmutable::createFromFormat(
    'Y-m-d H:i:s.u',
    '2025-03-08 14:30:15.123456',
    new DateTimeZone('UTC')
);

if ($time === false) {
    throw new RuntimeException('日期解析失败');
}

echo $time->format('Y-m-d H:i:s.u');
// 2025-03-08 14:30:15.123456
?>

在 PHP 中,date() 格式中的 u 和日期对象的微秒能力不可混为一谈。若数据来源只有整数秒,再改用日期对象也不会凭空获得真实的小数秒。

八、日期解析、无效日期与宽松输入

date() 只负责格式化,不验证一个用户提交的字符串。例如,2025-02-30 不是有效的日历日期。若把它交给宽松解析函数,解析器可能会将其归一化到另一个日期,而不是按业务要求直接拒绝。因此,涉及预约、账单周期或生日等输入时,必须显式校验。

可以使用 DateTimeImmutable::createFromFormat() 按指定格式解析,并检查解析错误,同时比较解析后的值与原始输入:

<?php
$input = '2025-02-30';

$date = DateTimeImmutable::createFromFormat(
    '!Y-m-d',
    $input,
    new DateTimeZone('UTC')
);

$errors = DateTimeImmutable::getLastErrors();

// PHP 8.2 及之后,若没有警告或错误,getLastErrors() 可能返回 false。
$hasErrors = $errors !== false
    && ($errors['warning_count'] > 0 || $errors['error_count'] > 0);

if (
    $date === false
    || $hasErrors
    || $date->format('Y-m-d') !== $input
) {
    echo '日期无效';
} else {
    echo $date->format('Y-m-d');
}
?>

这里的 ! 会把未指定的字段重置到默认值,避免当前时分秒等信息残留。对于只接受 YYYY-MM-DD 的场景,严格格式和回读比较能帮助拦截部分自动修正后的非法输入。实际校验还应根据业务要求处理输入类型、空值和范围。

strtotime() 能解析许多英文日期表达式,但其输入规则相对宽松,诸如“下周一”这样的表达式还依赖调用时刻和时区。它适合受控输入或表达式解析,不适合不经校验地充当严格的用户日期验证器。

九、什么时候选 date(),什么时候用 DateTimeImmutable

只需要把已有时间戳格式化为简单字符串时,date() 通常够用。下面这些场景则更适合使用 DateTimeImmutable 或相关日期时间类:

  • 需要解析指定格式,并判断输入是否合法。
  • 需要从一个时间点转换到另一个时区。
  • 需要进行日期加减、比较,或处理带微秒的时间。
  • 希望避免直接修改已有日期对象,降低共享状态带来的意外变化。

例如,将 UTC 时间点显示为纽约当地时间:

<?php
$instant = new DateTimeImmutable(
    '2025-03-08T18:00:00+00:00'
);

$newYorkTime = $instant->setTimezone(
    new DateTimeZone('America/New_York')
);

echo $newYorkTime->format('Y-m-d H:i:s P e');
?>

DateTimeImmutable 的方法返回新的对象,不会直接改写原对象。进行多步转换时,明确保存返回值可以让代码更易理解。

十、中文月份和星期不是 date() 的本地化功能

D、l、M、F 等字符输出的是英文星期或月份名称。设置系统区域、修改时区,或调用 setlocale(),都不会让 date() 自动输出中文月份名称。

如果需要按语言和地区格式化日期,例如中文星期、当地习惯的月份顺序或地区化数字,应考虑 PHP 的 IntlDateFormatter(需要相应的 Intl 扩展)。这类需求属于国际化格式化,而不是单纯使用 date() 的格式字符。

如果页面只需要固定的中文年月日,也可以组合数字和中文文本:

<?php
date_default_timezone_set('Asia/Shanghai');

echo date('Y年n月j日');
?>

这适用于固定的中文展示格式,不适合需要多语言、地区习惯切换或复杂本地化规则的应用。

十一、输出安全:格式化日期不等于转义内容

date() 会生成字符串,但它不是 HTML 转义函数,也不会自动对输入进行过滤。普通的固定格式日期通常只包含数字、分隔符和固定文本;不过,如果输出内容还包括用户可控的格式字符串、标签文本或其他数据,仍应根据输出上下文进行编码。

在 HTML 页面中输出动态文本,通常应使用 htmlspecialchars(),并选择合适的字符集与标志位:

<?php
date_default_timezone_set('Asia/Shanghai');

$displayDate = date('Y-m-d H:i:s');

echo htmlspecialchars(
    $displayDate,
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);
?>

这并不意味着日期格式化本身能防止跨站脚本攻击。HTML、JavaScript、URL、CSS 和 SQL 各自有不同的安全边界,必须针对最终使用场景处理数据。不要因为一个值“看起来像日期”就把它直接拼进 SQL;数据库查询应使用参数化语句。文件名中的时间戳也不能代替权限检查或路径校验。

如果格式字符串由用户提供,应限制允许的格式,避免用户控制输出内容或利用格式规则改变程序行为。更稳妥的做法是由程序端选定格式,再将格式化结果作为普通数据输出。

十二、常见错误速查

常见问题原因建议
分钟位置显示成月份把分钟写成了 m,正确字符是 i日期用 m,分钟用 i
时间差了数小时默认时区与预期时区不同,或把本地时间当成 UTC明确设置时区,并区分时间点与本地时间
日期远在未来把毫秒时间戳当作秒使用确认单位,并在必要时将毫秒换算为秒
微秒输出为零date() 使用秒级时间戳,u 不会补出真实微秒使用带微秒的日期对象和真实高精度数据
年初周数显示不正确把 ISO 周数 W 和日历年份 Y 混用用 o-W 表示 ISO 周年和周数
字母被替换成数字或英文格式中的字母被当作特殊格式字符用反斜杠转义要原样输出的字符
中文星期没有生效date() 的星期名称不是本地化输出使用 Intl 扩展或固定的中文展示规则
无效日期被“自动调整”采用了宽松解析,没有严格检查日期有效性使用指定格式解析,并检查警告、错误和回读结果

总结

date() 的核心用途是把 Unix 秒级时间戳按默认时区和指定格式转换成字符串。记住 Y-m-d H:i:s 中年月日、小时、分钟和秒的字符差异,明确设置时区,并在格式字符串中转义字面字符,就能覆盖大多数简单展示需求。

如果涉及严格输入校验、时区转换、日期运算、微秒或多语言格式化,应选择相应的日期时间类或 Intl 功能。无论日期如何生成,输出到 HTML、SQL 或其他上下文时,都要遵循对应的编码与安全规则。

© 版权声明

相关文章