前言
strftime()是 PHP 里按本地化格式输出时间的函数:它根据当前设置的区域(locale),把日期时间格式化成"2026年10月6日 星期二"或者"Tuesday, 6 October 2026"这样的形式。它和date()的分工是:date()只输出英文和固定格式,strftime()会跟随 locale 变化,星期名、月份名、日期顺序都会变。
但必须先把最关键的一条讲清楚:strftime()和gmstrftime()在 PHP 8.1.0 中已被废弃(deprecated),计划在 PHP 9.0 移除。在 PHP 8.1 及以上调用它会产生E_DEPRECATED通知。官方给出的迁移方向是:不依赖区域的就换成date()或DateTime::format(),需要本地化的就换成IntlDateFormatter::format()(需要intl扩展)。
也就是说,这个函数现在只有两类读者:一是在 PHP 8.0 及更早版本上维护老项目的开发者,二是正在把老代码迁移出去的人。本文既讲它的完整用法——因为老项目还得看懂、还得改对——也讲迁移的目标写法。
另外要澄清一个常见误解:strftime()的输出不是 PHP 自己实现的,而是直接调用 C 库的strftime()。所以同一段格式串在不同操作系统上结果可能不一样,这是它的固有缺陷,也正是它被废弃的原因之一。官方手册明确说明:并非所有转换说明符都被各种 C 库支持,不被支持的那些在 PHP 里也不可用。
一、基本用法与 locale 设置
函数签名的形状是strftime($format, $timestamp = null):第一个参数是格式串,第二个是可选的 Unix 时间戳,省略时用当前时间。gmstrftime()参数完全一样,区别只在于它按 UTC 解释时间戳,而strftime()按本地时区。
<?php // 适用于 PHP 7.0 ~ 8.0;PHP 8.1 起本函数已废弃
$ts = mktime(15, 30, 0, 10, 6, 2026);
echo strftime('%Y-%m-%d %H:%M:%S', $ts), PHP_EOL;
echo gmstrftime('%Y-%m-%d %H:%M:%S', $ts), PHP_EOL; // 按 UTC 输出
要让输出真的"本地化",必须先设置区域,而且必须指定LC_TIME这个类别:
<?php // 适用于 PHP 7.0 ~ 8.0;PHP 8.1 起本函数已废弃
$ts = mktime(0, 0, 0, 10, 6, 2026);
// setlocale 成功返回新的 locale 字符串,失败返回 false
$old = setlocale(LC_TIME, '0'); // 先记下当前设置
$ok = setlocale(LC_TIME, 'zh_CN.UTF-8'); // Linux 上的中文区域名
if ($ok === false) {
// Windows 上的区域名格式不同,常见写法有 'chs'、'Chinese_China.936'
$ok = setlocale(LC_TIME, 'chs');
}
if ($ok === false) {
echo '区域设置失败,将退回英文输出', PHP_EOL;
}
echo strftime('%Y年%m月%d日 %A', $ts), PHP_EOL;
setlocale(LC_TIME, $old); // 用完恢复,避免影响后续逻辑
setlocale()的返回值一定要检查——区域设置失败时它返回false,而strftime()仍然会正常输出,只是输出的是默认的英文。这是最典型的"代码没报错但结果不对"的情形。另外,区域名在不同系统上差异很大:Linux 上是zh_CN.UTF-8这种形式,Windows 上是chs、Chinese_China.936之类;而且服务器上没装对应的 locale 包时(精简版 Docker 镜像尤其常见)zh_CN.UTF-8会直接失败。这也是为什么依赖strftime()做本地化在生产环境里非常脆弱。
二、转换说明符全表
strftime()的转换说明符来自 C 标准,以百分号开头,大部分字母的含义和date()完全不同(例如%m是月份而不是分钟,分钟是%M),照date()的习惯往里填必错。
%c | 首选的日期时间表示 | Tue Oct 6 15:30:00 2026 |
用一张表就能看出几个容易搞混的点:%m是月、%M是分;%d是日、%D在部分 C 库里是%m/%d/%y的简写(并不是"日"的另一种写法);%U和%W的区别只在于"一周从周日还是周一开始",而%V才是 ISO 8601 周数,跨年附近三者结果可能差一周。
一个实测脚本,用固定时间戳把常见说明符一次性打出来(注意%e、%T、%R、%D等在 Windows 上不受支持,官方手册已明确说明这一点,所以在 Windows 上跑这段会看到原样输出或异常结果):
<?php // 适用于 PHP 7.0 ~ 8.0;PHP 8.1 起本函数已废弃
$ts = mktime(15, 30, 0, 10, 6, 2026); // 2026-10-06 15:30:00
$specs = ['%Y', '%y', '%m', '%d', '%H', '%M', '%S', '%A', '%a', '%B',
'%j', '%u', '%w', '%U', '%V', '%W', '%z', '%Z', '%%'];
foreach ($specs as $s) {
printf("%-4s => %s\n", $s, strftime($s, $ts));
}
三、迁移到date()与IntlDateFormatter
官方给出的迁移路径分成两条,判断标准只有一句话:输出要不要跟随语言环境。
不需要本地化(只用数字、或者英文缩写就够)的,直接换成DateTime::format(),格式字符沿用date()的那一套:
strftime() | date()/DateTime::format() | 说明 |
|---|
%Y%m%d%H%M%S | YmdHis | 拼文件名、时间戳串 |
%j | z加 1 | date('z')从 0 开始,需注意差一 |
<?php // 适用于 PHP 8.0+
$dt = new DateTimeImmutable('2026-10-06 15:30:00');
echo $dt->format('Y-m-d H:i:s'), PHP_EOL; // 2026-10-06 15:30:00
echo $dt->format('l, j F Y'), PHP_EOL; // Tuesday, 6 October 2026(英文)
需要真正本地化(中文月份名、中文星期、按区域习惯排列的日期)的,用IntlDateFormatter,它由 ICU 驱动,不依赖操作系统的 locale 安装情况,这也是它比strftime()可靠的地方:
<?php // 适用于 PHP 8.0+,需要 intl 扩展
$ts = (new DateTimeImmutable('2026-10-06 15:30:00'))->getTimestamp();
$fmt = new IntlDateFormatter(
'zh_CN', // 区域
IntlDateFormatter::LONG, // 日期样式
IntlDateFormatter::SHORT, // 时间样式
'Asia/Shanghai', // 时区
IntlDateFormatter::GREGORIAN
);
echo $fmt->format($ts), PHP_EOL; // 2026年10月6日 15:30(随 ICU 版本略有差异)
// 也可以用自定义模式串,符号含义与 strftime 不同
$fmt2 = new IntlDateFormatter('zh_CN', IntlDateFormatter::NONE,
IntlDateFormatter::NONE, 'Asia/Shanghai', IntlDateFormatter::GREGORIAN,
'yyyy年MM月dd日 EEEE');
echo $fmt2->format($ts), PHP_EOL;
注意IntlDateFormatter的模式串符号(yyyy、MM、dd、EEEE)用的是 ICU 的语法,和strftime()的%Y、%m完全是两套东西,别混着写。ICU 的输出在不同 ICU 版本间可能有细微差别,所以对格式有严格断言的地方(比如测试用例)要预期到这种差异。
如果你的运行环境实在装不了intl,还有一个折中办法:把中文星期、中文月份做成常量数组,用date('w')、date('n')去查表。这样做的好处是完全可控、不依赖任何扩展和系统 locale;代价是要自己维护,且只有你支持的那几种语言。
常见坑点
- ❌ 在 PHP 8.1 及以上的项目里继续调用
strftime(),然后被E_DEPRECATED通知刷屏
✅ 迁到date()/DateTime::format()或IntlDateFormatter;strftime()计划在 PHP 9.0 移除。
- ❌ 把
date()的格式字符直接塞给strftime()
✅ 两套符号完全不同:strftime('%m')是月份,date('m')也是月份,但strftime('%M')是分钟而date('M')却是月份缩写——必须逐个对照。
- ❌ 调了
setlocale()但不检查返回值,也不设LC_TIME
✅ 只设LC_TIME;检查返回值是否为false,失败时要有降级方案。
- ❌ 依赖
%e、%T、%R、%D这类说明符做跨平台输出,或者用strftime()处理 1970 年之前的时间
✅ 官方手册说明并非所有说明符都被各平台 C 库支持,%e、%T、%R、%D等在 Windows 上不受支持;负时间戳(1970 年之前)在 Windows 和部分系统上同样不被支持。跨平台一律改用date()体系。
- ❌ 用
%U或%W当 ISO 周数
✅ ISO 8601 周数是%V;%U(周日起)与%W(周一起)在跨年附近可能与%V相差一周。
- ❌ 用
strftime()输出"当前时间戳"或做时间运算
✅ 时间运算用DateTime系列做加减(modify()、add()),格式化和运算是两件事。
- ❌ 以为
strftime()输出的一定是本地语言
✅ 区域设置失败时它会安静地输出默认英文;服务器缺 locale 包(精简 Docker 镜像常见)时会直接命中这个情况。
- ❌ 在同一个请求里到处
setlocale()却不恢复
✅setlocale()改的是进程级设置,用完恢复原值,否则会把同一请求里其他模块的格式化输出一起改掉。
总结
| 语言支持 | strftime()已废弃(PHP 8.1 起产生E_DEPRECATED),计划 PHP 9.0 移除 |
| 配套函数 | gmstrftime()同样已废弃,差别只在于按 UTC 解释时间戳 |
| 本地化前提 | 必须先setlocale(LC_TIME, ...)并检查返回值 |
| 平台差异 | 实现直接来自 C 库,说明符支持情况随平台变化;Windows 缺若干说明符 |
| 输出格式 | 百分号说明符,与date()的格式字符完全不是一套 |
| 不本地化的替代 | date()/DateTime::format() |
| 要本地化的替代 | IntlDateFormatter(需intl扩展,由 ICU 驱动) |
| 最隐蔽的坑 | 区域设置失败时静默输出英文,"没报错但结果不对" |
如果你手上是 PHP 8.1 及以上的项目,结论很干脆:不要再用strftime(),也别引第三方兼容包去"续命"——那些包用 ICU 重实现,输出和原来的 C 库版本本来就有差异,不如一次性迁到IntlDateFormatter或date()。如果是 PHP 8.0 及更早的老项目要长期维护,那么至少做到两件事:所有setlocale()都检查返回值,所有平台特定的说明符都不要用——这样等你要迁移的时候,工作量会小得多。