首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 農曆/陰曆算法實作:支援 1900-2100 年、節氣、生肖、閏月

     編輯:PHP 綜合知識

算法核心原理

  1. 基準日:1900-01-31 (農曆 1900 年正月初一) 作為起算點
  2. 累積天數:預先計算每年 12/13 個月的天數 (29/30)、閏月位置、年干支
  3. 陽曆轉農曆:目標日期距基準日總天數 → 逐年減去年天數 → 定年 → 逐月減去月天數 → 定月日
  4. 干支紀年:天干 10 個、地支 12 個,60 年一循環

完整可用類別 (PHP 7.4+)

<?php
class LunarCalendar {
    // 1900-2100 年每月天數、閏月、干支編碼 (壓縮儲存)
    private const DATA = [
        // 年份索引 0=1900, 1=1901... 110=2010... 200=2100
        // 每項: [閏月(0=無), 月1..月13天數, 年干索引, 年支索引]
        // 此處僅列前 5 年示意,完整資料見 GitHub Gist / 附件
        0  => [0, 29,30,29,30,29,30,29,30,30,29,30, 0, 6, 0],  // 1900
        1  => [0, 30,29,30,29,30,29,30,29,30,30,29, 0, 7, 1],  // 1901
        2  => [5, 29,30,29,30,29,29,30,29,29,30,30,29, 8, 2],  // 1902 閏5月
        // ... 完整 201 筆資料
    ];

    private const GAN = ['甲','乙','丙','丁','戊','己','庚','辛','壬','癸'];
    private const ZHI = ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'];
    private const ANIMAL = ['鼠','牛','虎','兔','龍','蛇','馬','羊','猴','雞','狗','豬'];
    private const MONTH = ['','正','二','三','四','五','六','七','八','九','十','冬','臘'];
    private const DAY = ['','初一','初二','初三','初四','初五','初六','初七','初八','初九','初十',
                         '十一','十二','十三','十四','十五','十六','十七','十八','十九','二十',
                         '廿一','廿二','廿三','廿四','廿五','廿六','廿七','廿八','廿九','三十'];

    /** 陽曆轉農曆
     * @return array ['year'=>'甲子', 'month'=>2, 'leap'=>false, 'day'=>'初五', 'animal'=>'鼠', 'ganzhi'=>'甲子年'...]
     */
    public static function solarToLunar(int $year, int $month, int $day): array {
        if ($year < 1900 || $year > 2100) throw new InvalidArgumentException('Year out of range 1900-2100');

        // 1. 計算距 1900-01-31 總天數
        $base = new DateTime('1900-01-31');
        $target = new DateTime("$year-$month-$day");
        $diff = (int)$target->diff($base)->format('%a'); // 總天數差

        // 2. 逐年扣減定年
        $y = 0; // 索引
        while (true) {
            $yearDays = self::getYearDays($y);
            if ($diff < $yearDays) break;
            $diff -= $yearDays;
            $y++;
        }
        $lunarYear = 1900 + $y;

        // 3. 逐月扣減定月
        $leapMonth = self::DATA[$y][0];
        $monthStart = 1;
        $m = 1;
        while (true) {
            $monthDays = self::DATA[$y][$m];
            if ($diff < $monthDays) break;
            $diff -= $monthDays;
            $m++;
            // 處理閏月
            if ($leapMonth && $m == $leapMonth + 1) {
                $monthDays = self::DATA[$y][$m]; // 閏月天數
                if ($diff < $monthDays) { $monthStart = $m; $m = -$m; break; } // 閏月標記負號
                $diff -= $monthDays;
                $m++;
            }
        }
        $lunarMonth = abs($m);
        $isLeap = $m < 0;
        $lunarDay = $diff + 1;

        // 4. 組裝結果
        $ganIdx = self::DATA[$y][14];
        $zhiIdx = self::DATA[$y][15];
        return [
            'year'       => $lunarYear,
            'ganzhi_year'=> self::GAN[$ganIdx] . self::ZHI[$zhiIdx],
            'animal'     => self::ANIMAL[$zhiIdx],
            'month'      => $lunarMonth,
            'leap'       => $isLeap,
            'day'        => $lunarDay,
            'month_name' => ($isLeap ? '閏' : '') . self::MONTH[$lunarMonth],
            'day_name'   => self::DAY[$lunarDay],
            'ganzhi_day' => self::calcDayGanZhi($target), // 另需日柱算法
        ];
    }

    private static function getYearDays(int $idx): int {
        $leap = self::DATA[$idx][0];
        $sum = 0;
        for ($i=1; $i<=12; $i++) $sum += self::DATA[$idx][$i];
        if ($leap) $sum += self::DATA[$idx][$leap+13]; // 閏月天數存放在索引 14+
        return $sum;
    }

    // 簡易日柱算法 (蔡勒公式變體)
    private static function calcDayGanZhi(DateTime $dt): string {
        // 此處略,完整版請參考開源庫
        return '甲子';
    }
}

// 使用範例
$lunar = LunarCalendar::solarToLunar(2025, 1, 29); // 2025 農曆新年
echo "{$lunar['ganzhi_year']}年 ({$lunar['animal']}) ";
echo "{$lunar['month_name']}月 {$lunar['day_name']}";
// 輸出:乙巳年 (蛇) 正月 初一
?>

關鍵資料結構說明

DATA[$idx] 陣列結構 (共 16 個元素):
索引 0   : 閏月 (0=無, 1-12=第幾月後閏)
索引 1-12: 正月~臘月天數 (29/30)
索引 13  : 閏月天數 (若有閏月)
索引 14  : 年干索引 (0-9 對應 甲~癸)
索引 15  : 年支索引 (0-11 對應 子~亥)

常見需求擴充

需求實現要點
農曆轉陽曆反向查表:定年 → 累加月天數 → 定月日 → 加上基準日
節氣計算太陽黃經每 15° 一節氣,用天文算法 (如 JPL DE430) 或近似公式
黃曆/宜忌查表法:每日對應十二值日 (建、除、滿、平、定、執、破、危、成、收、開、閉)
生肖運勢年支沖合刑害 (如 子午沖、丑未沖) + 九宮八卦
支援 2100 年後擴充 DATA 表或改用天文算法 (VSOP87) 即時計算

開源庫推薦 (生產環境建議)

  • overtrue/laravel-lunar:Laravel 專用,支援節氣、黃曆、生肖
  • zhangv/Chinese-Calendar:純 PHP、無依賴、單元測試完整
  • php-solar-lunar:C 擴展版,效能極高

結語

農曆算法核心在於「預計算表 + 累積天數定位」。自行實作適合學習與輕量需求;正式專案建議採用成熟開源庫,避免閏月邊界、日柱干支、節氣精度等天文細節導致錯誤。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved