必须用 DateTime::createFromFormat() 构造对象并 clone 后 format,避免引用传递导致日期重复;起止时间需归一化到零点;ThinkPHP 模型获取器可能干扰日期输出,应声明字段类型并缓存变量。

字符串比较日期导致循环提前终止
直接用 $start 比较格式化后的日期字符串(如 <code>"1/2/2015" 和 "1/30/2015")会按字典序判断,"1/2/" 的 '2' 小于 "1/30/" 的 '3',结果 "1/2/2015" > "1/30/2015" 成立,循环只跑几次就退出——这不是“重复天”,是根本没跑全。
- 别用
strtotime()+ 字符串拼接做循环控制,解析模糊、时区易错、月末加减不可靠 - 必须用
DateTime::createFromFormat()构造对象,再用DateInterval('P1D')递增 - 验证
$start和$end是否为有效DateTime实例,空对象会导致静默失败
DateTime 对象未 clone 导致所有日期指向同一时间点
常见写法:$dates[] = $start->format(...) 然后 $start->add($interval) —— 看似没问题,但 $start 是引用传递,format() 取的是当前值,等下次循环再 format() 时,它已经被 add() 改过。最终数组里全是最后一个日期。
- 每次存入前必须
clone $start或用$start->format()后立即保存字符串 - 更稳妥写法:
$dates[] = (clone $start)->format($format) - 不要在循环体外复用同一个
DateTime实例做多次format()
起止时间未归一化到同一天零点引发边界错位
比如起始设为 "2026-08-06 14:00:00",结束设为 "2026-08-10 09:00:00",用 P1D 递增时,第 5 次 add() 后变成 "2026-08-11 14:00:00",已超结束时间,但因含时间部分,$start 还成立一次,多出一天。
- 若只需按“日”遍历,构造时统一设为当天
00:00:00:$start->setTime(0, 0, 0) - 或改用
$start->format('Y-m-d') format('Y-m-d')控制,但丧失时间精度 - 注意
DateInterval加减严格按日历计算,跨月、闰年、夏令时自动处理,无需手动干预
ThinkPHP 5.1 中模型获取器干扰日期字段输出
如果在模型里写了 getCreateTimeAttr() 返回 Carbon 实例,又在循环中反复调用 $model->create_time->format(...),而该字段本身是字符串或时间戳,获取器可能被多次触发或缓存失效,造成时间偏移或重复渲染。
立即学习“PHP免费学习笔记(深入)”;
- 确认数据库字段类型是
datetime或int,并在模型$type中声明对应转换 - 避免在循环内对同一模型实例反复取时间字段再格式化,提取后缓存变量
- 若需批量格式化,优先用
collection->each()或array_map(),而非 foreach + 手动 format
DateTime 对象的状态可变性——它不像字符串那样“拿来即用”,每一次 add()、modify() 都会改变原实例,不显式 clone 或及时 format,就会在循环里悄悄复用同一时间点。



















