Holiday:去掉三处 400 天上限,农历/节日/节气在全量范围下不再丢失
## 问题 lunarRange / traditionalRange 都有 `$i < 400` 硬上限。 2026-10-30 客户端改成全量同步(1970~2100,47846 天)后, 这个「防异常」的保险变成「削正常功能」: - 只算前 400 天 ⇒ **今天的农历是空的** - 传统节日同样只剩前 400 天 ⇒ 今年的春节/中秋全丢 solarTermRange 另有一个问题:只取首末两年, 130 年范围下只算 1970 和 2100 的节气,中间 128 年全丢。 ## 修法 - 三处统一改为:夹到 Lunar::MIN_YEAR~MAX_YEAR,再按 MAX_LUNAR_YEARS(30 年)截断,去掉按天计数上限 - solarTermRange 改为逐年遍历 ## 实测(部署后) 今天 2026-11-02 → 廿四(修复前为空) 今年 11 个传统节日、24 个节气,齐全 2021~2031 农历 4017 条(修复前 400),节气 264 条(修复前 48) 1970~2100 农历 10958 条 / 0.47 秒(按 30 年上限截断,符合设计) ## 注意 本文件不在客户端仓库,需单独上传服务器。 验证前必须比对服务器 md5,否则会拿旧代码验出假结果(本次踩过)。
This commit is contained in:
+71
-8
@@ -399,9 +399,24 @@ final class Holiday
|
||||
if ($t === false || $end === false || $end < $t) {
|
||||
return [];
|
||||
}
|
||||
$years = [(int)substr($from, 0, 4)];
|
||||
if ((int)substr($to, 0, 4) !== $years[0]) {
|
||||
$years[] = (int)substr($to, 0, 4);
|
||||
// 🔴 2026-11-02:原来只取「首年 + 末年」两年。
|
||||
// 全量范围 1970~2100 下 ⇒ 只算了 1970 和 2100 的节气,
|
||||
// 中间 128 年全丢(用户会觉得"日历上没节气")。
|
||||
// 改成逐年遍历,并夹紧到农历算法支持范围 + 年数上限。
|
||||
$yFrom = (int)substr($from, 0, 4);
|
||||
$yTo = (int)substr($to, 0, 4);
|
||||
if ($yFrom < Lunar::MIN_YEAR) {
|
||||
$yFrom = Lunar::MIN_YEAR;
|
||||
}
|
||||
if ($yTo > Lunar::MAX_YEAR) {
|
||||
$yTo = Lunar::MAX_YEAR;
|
||||
}
|
||||
if ($yTo - $yFrom + 1 > self::MAX_LUNAR_YEARS) {
|
||||
$yTo = $yFrom + self::MAX_LUNAR_YEARS - 1;
|
||||
}
|
||||
$years = [];
|
||||
for ($yy = $yFrom; $yy <= $yTo; $yy++) {
|
||||
$years[] = $yy;
|
||||
}
|
||||
foreach ($years as $y) {
|
||||
foreach (self::SOLAR_TERMS as $idx => $name) {
|
||||
@@ -435,9 +450,25 @@ final class Holiday
|
||||
if ($t === false || $end === false || $end < $t) {
|
||||
return [];
|
||||
}
|
||||
// 逐日扫区间,转农历后查表 —— 区间很短(月视图 ≤42 天),成本可忽略。
|
||||
// 这样天然处理"跨月/跨年"(如腊月二十三可能落在次年 1 月)。
|
||||
for ($i = 0; $t <= $end && $i < 400; $i++, $t = strtotime('+1 day', $t)) {
|
||||
// 逐日扫区间,转农历后查表 —— 天然处理"跨月/跨年"
|
||||
// (如腊月二十三可能落在次年 1 月)。
|
||||
//
|
||||
// 🔴 2026-11-02:原来有 `$i < 400` 的硬上限。改成全量同步后请求范围是
|
||||
// 1970~2100(47846 天)⇒ 只算前 400 天,**今年的春节/中秋全丢**。
|
||||
// 改为与 lunarRange 一致的「夹范围 + 年数上限」。
|
||||
$minT = (int)strtotime(sprintf('%04d-01-31', Lunar::MIN_YEAR));
|
||||
$maxT = (int)strtotime(sprintf('%04d-12-31', Lunar::MAX_YEAR));
|
||||
if ($t < $minT) {
|
||||
$t = $minT;
|
||||
}
|
||||
if ($end > $maxT) {
|
||||
$end = $maxT;
|
||||
}
|
||||
$yearsLimitT = (int)strtotime('+' . self::MAX_LUNAR_YEARS . ' years', $t);
|
||||
if ($end > $yearsLimitT) {
|
||||
$end = $yearsLimitT;
|
||||
}
|
||||
for (; $t <= $end; $t = strtotime('+1 day', $t)) {
|
||||
$date = date('Y-m-d', $t);
|
||||
$y = (int)date('Y', $t); $m = (int)date('n', $t); $d = (int)date('j', $t);
|
||||
$l = Lunar::solarToLunar($y, $m, $d);
|
||||
@@ -695,6 +726,19 @@ final class Holiday
|
||||
*
|
||||
* ⚠️ 每天的农历都要出,不能只出初一十五 —— 用户要的是"看日历就知道今天农历几号"。
|
||||
*/
|
||||
/**
|
||||
* 单次请求最多算多少年的农历。
|
||||
*
|
||||
* 🔴 2026-11-02:原来是「最多 400 天」的硬上限,而客户端改成全量同步后
|
||||
* 请求范围是 1970~2100(47846 天)⇒ 只返回前 400 天的农历,
|
||||
* 导致**今天的农历是空的**。
|
||||
*
|
||||
* 改成按年计:客户端实际只需「今天 ±5 年」,
|
||||
* 留 30 年余量足够,同时防止有人手拼 1900~2100 把服务器拖死
|
||||
* (算 7 万天农历约需数十秒)。
|
||||
*/
|
||||
private const MAX_LUNAR_YEARS = 30;
|
||||
|
||||
public static function lunarRange(string $from, string $to): array
|
||||
{
|
||||
$out = [];
|
||||
@@ -703,8 +747,27 @@ final class Holiday
|
||||
if ($t === false || $end === false || $end < $t) {
|
||||
return [];
|
||||
}
|
||||
// 上限保险:范围异常大时不至于把请求拖死(正常月视图最多 42 天)
|
||||
for ($i = 0; $t <= $end && $i < 400; $i++, $t = strtotime('+1 day', $t)) {
|
||||
|
||||
// ① 夹到农历算法支持的范围(1900-01-31 ~ 2100-12-31),
|
||||
// 否则 solarToLunar 会一路返回 null,白跑空转。
|
||||
$minT = (int)strtotime(sprintf('%04d-01-31', Lunar::MIN_YEAR));
|
||||
$maxT = (int)strtotime(sprintf('%04d-12-31', Lunar::MAX_YEAR));
|
||||
if ($t < $minT) {
|
||||
$t = $minT;
|
||||
}
|
||||
if ($end > $maxT) {
|
||||
$end = $maxT;
|
||||
}
|
||||
|
||||
// ② 年数上限:超出就截断到 MAX_LUNAR_YEARS 年末
|
||||
$yearsLimitT = (int)strtotime('+' . self::MAX_LUNAR_YEARS . ' years', $t);
|
||||
if ($end > $yearsLimitT) {
|
||||
$end = $yearsLimitT;
|
||||
}
|
||||
|
||||
// ③ 逐日生成(农历必须逐日算,无法按年跳)
|
||||
// 原来的 `$i < 400` 计数制已由上面的年数上限取代。
|
||||
for (; $t <= $end; $t = strtotime('+1 day', $t)) {
|
||||
$date = date('Y-m-d', $t);
|
||||
$y = (int)date('Y', $t); $m = (int)date('n', $t); $d = (int)date('j', $t);
|
||||
$l = Lunar::solarToLunar($y, $m, $d);
|
||||
|
||||
Reference in New Issue
Block a user