PHP 用 mktime 生成月份数组时英国夏令时调表后出现重复3月无2月异常
异常成因
这个问题和RethinkDB的eachPosTime函数完全无关,核心是代码长期存在的参数依赖bug,刚好被英国夏令时切换的时间点触发暴露:
- PHP的
mktime()函数有明确的参数缺省规则:凡是调用时未手动传入的参数,会直接取当前系统本地时间对应的年、月、日值。你写的两个版本的函数里,调用mktime(0,0,0,$i)时,只传入了时、分、秒、月份四个参数,没有手动传入“日期”和“年份”两个值,这两个参数会自动使用当前系统的数值。 - 英国2024年的夏令时切换节点是3月31日,切换完成后系统的本地时间中,“日”的数值固定为31,“年”为2024年。当循环到
$i=2(要生成2月的时间节点)时,mktime实际拿到的参数组合是:时0、分0、秒0、月2、日31、年2024。 - 2024年是闰年,2月最多只有29天,根本不存在2月31日这个时间点。PHP内部对
mktime的时间计算会自动做归一化:超出当月天数的时间会顺次向后顺延,2月29日之后剩余的2天偏移,会直接滚到3月2日。这就导致循环到2月的位置时,计算出来的月份实际是3月;等循环到$i=3时,正常取到3月的日期,最终结果就出现了两个3月、完全缺失2月的异常。 - 为什么之前运行正常?因为在夏令时切换前,英国本地时间的系统“日”值始终小于30,无论循环到哪个月份,用当前的日值计算时间都不会超出当月的最大天数,不会触发跨月偏移。只有当系统进入3月31日这个有31号的日期、恰逢夏令时切换,才把这个潜藏的bug触发出来。你之前尝试修改的第二版函数,依然在调用
mktime(0,0,0,$m)时没有手动传入日期、年份参数,还是在依赖系统当前的日值做计算,所以问题不会有任何改善。
修复方案
核心思路是彻底去掉mktime对系统当前参数的隐式依赖,手动传入明确、固定的计算基准值,从根源上避免时间越界:
- 最直接的改法:手动固定传入每个月的参考日期(统一取每月1号即可)和参考年份,不要依赖函数的缺省参数,示例代码:
function months($month_format="F") { $array = array(); // 固定取当前年作为参考基准 $reference_year = date("Y"); for ($i = 1; $i <=12; $i++) { // 手动传全所有参数,不再依赖缺省的日、年值 $timestamp = mktime(0,0,0,$i, 1, $reference_year); $array[$i]['string'] = date($month_format, $timestamp); $array[$i]['int'] = date('m', $timestamp); } return $array; } - 更稳妥的替代写法:直接用PHP原生的DateTime类构造固定日期对象,完全绕开
mktime的参数归一化坑,也能兼容不同时区、夏令时的边界场景,示例代码:function months($month_format="F") { $array = array(); $reference_year = date("Y"); for ($i = 1; $i <=12; $i++) { // 直接按固定模板构造日期,不读取任何系统当前的可变时间值 $dt = new DateTime("$reference_year-$i-01"); $array[$i]['string'] = $dt->format($month_format); $array[$i]['int'] = $dt->format('m'); } return $array; }
额外提示:这个bug的本质是代码对隐式参数的滥用,和夏令时时钟拨快本身没有直接因果关系,哪怕不切换夏令时,只要系统时间走到当月有31号的日期,循环到天数不足31天的月份(比如2月、4月、6月、9月、11月)时,都会触发同类的跨月偏移问题。
内容的提问来源于stack exchange,提问作者AGradePHP
相关产品推荐
相关产品推荐

