本文目录导读:

在PHP中实现公历与农历的互转,有几种常见的方法:
方法1:使用第三方库(推荐)
安装 overtrue/lunar 库
composer require overtrue/lunar
公历转农历
<?php
require 'vendor/autoload.php';
use Overtrue\Lunar\Lunar;
// 公历转农历
$lunar = Lunar::fromDate('2024-01-15');
echo $lunar->format('Y-m-d'); // 输出:2023-十二月初五
// 或者使用对象方式
$lunar = Lunar::make(2024, 1, 15);
echo $lunar->getYear() . '年'; // 2023年
echo $lunar->getMonth() . '月'; // 十二月
echo $lunar->getDay() . '日'; // 初五
农历转公历
<?php
require 'vendor/autoload.php';
use Overtrue\Lunar\Lunar;
// 农历转公历
$lunar = Lunar::make(2023, 12, 5);
$solar = $lunar->toSolar();
echo $solar->format('Y-m-d'); // 输出:2024-01-15
方法2:自定义函数实现
这是一个完整的自定义实现:
<?php
class LunarCalendar {
// 1900-2100年农历数据
private static $lunarInfo = [
0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,
0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,
0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,
0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,
0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,
0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,
0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,
0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,
0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,
0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x05ac0,0x0ab60,0x096d5,0x092e0,
0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,
0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,
0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,
0x05aa0,0x076a3,0x096d0,0x04afb,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,
0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,
0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50,0x06aa0,0x1a6c4,0x0aae0,
0x092e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,
0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,
0x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,
0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a4d0,0x0d150,0x0f252,
0x0d520
];
// 天干
private static $tianGan = ['甲','乙','丙','丁','戊','己','庚','辛','壬','癸'];
// 地支
private static $diZhi = ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'];
// 生肖
private static $shengXiao = ['鼠','牛','虎','兔','龙','蛇','马','羊','猴','鸡','狗','猪'];
// 农历月份
private static $lunarMonth = ['正','二','三','四','五','六','七','八','九','十','冬','腊'];
// 农历日期
private static $lunarDay = [
'初一','初二','初三','初四','初五','初六','初七','初八','初九','初十',
'十一','十二','十三','十四','十五','十六','十七','十八','十九','二十',
'廿一','廿二','廿三','廿四','廿五','廿六','廿七','廿八','廿九','三十'
];
/**
* 公历转农历
*/
public static function solarToLunar($year, $month, $day) {
// 基准日期:1900年1月31日(农历庚子年正月初一)
$baseDate = new DateTime('1900-01-31');
$targetDate = new DateTime("$year-$month-$day");
$offset = $baseDate->diff($targetDate)->days;
$lunarYear = 1900;
$lunarMonth = 1;
$lunarDay = 1;
$isLeap = false;
// 计算农历年
$i = 1900;
while ($offset > 0) {
$yearDays = self::lunarYearDays($i);
if ($offset < $yearDays) {
break;
}
$offset -= $yearDays;
$i++;
}
$lunarYear = $i;
// 计算农历月
$leapMonth = self::leapMonth($lunarYear);
$isLeap = false;
for ($j = 1; $j <= 12; $j++) {
$monthDays = self::lunarMonthDays($lunarYear, $j);
if ($offset < $monthDays) {
break;
}
$offset -= $monthDays;
if ($leapMonth > 0 && $j == $leapMonth) {
$leapDays = self::leapMonthDays($lunarYear);
if ($offset < $leapDays) {
$isLeap = true;
break;
}
$offset -= $leapDays;
$j--;
}
}
$lunarMonth = $j;
$lunarDay = $offset + 1;
// 获取天干地支年
$ganIndex = ($lunarYear - 4) % 10;
$zhiIndex = ($lunarYear - 4) % 12;
return [
'year' => $lunarYear,
'month' => $lunarMonth,
'day' => $lunarDay,
'isLeap' => $isLeap,
'yearName' => self::$tianGan[$ganIndex] . self::$diZhi[$zhiIndex] . '年',
'shengXiao' => self::$shengXiao[$zhiIndex] . '年',
'monthName' => ($isLeap ? '闰' : '') . self::$lunarMonth[$lunarMonth - 1] . '月',
'dayName' => self::$lunarDay[$lunarDay - 1]
];
}
/**
* 农历转公历
*/
public static function lunarToSolar($lunarYear, $lunarMonth, $lunarDay, $isLeap = false) {
// 计算农历年的总天数
$yearDays = self::lunarYearDays($lunarYear);
// 计算到农历年初的天数
$days = 0;
$leapMonth = self::leapMonth($lunarYear);
for ($i = 1; $i < $lunarMonth; $i++) {
$days += self::lunarMonthDays($lunarYear, $i);
if ($leapMonth > 0 && $i == $leapMonth) {
$days += self::leapMonthDays($lunarYear);
}
}
if ($isLeap && $leapMonth == $lunarMonth) {
$days += self::leapMonthDays($lunarYear);
}
$days += $lunarDay - 1;
// 基准日期
$baseDate = new DateTime('1900-01-31');
$baseDate->add(new DateInterval("P{$days}D"));
return [
'year' => (int)$baseDate->format('Y'),
'month' => (int)$baseDate->format('m'),
'day' => (int)$baseDate->format('d')
];
}
/**
* 计算农历年的总天数
*/
private static function lunarYearDays($year) {
$sum = 348; // 12个月平均天数
for ($i = 0x8000; $i > 0x8; $i >>= 1) {
$sum += (self::$lunarInfo[$year - 1900] & $i) ? 1 : 0;
}
return $sum + self::leapMonthDays($year);
}
/**
* 计算闰月天数
*/
private static function leapMonthDays($year) {
if (self::leapMonth($year)) {
return (self::$lunarInfo[$year - 1900] & 0x10000) ? 30 : 29;
}
return 0;
}
/**
* 判断闰月
*/
private static function leapMonth($year) {
return self::$lunarInfo[$year - 1900] & 0xf;
}
/**
* 计算农历月天数
*/
private static function lunarMonthDays($year, $month) {
return (self::$lunarInfo[$year - 1900] & (0x10000 >> $month)) ? 30 : 29;
}
}
// 使用示例
// 公历转农历
$lunarResult = LunarCalendar::solarToLunar(2024, 1, 15);
echo "农历日期:{$lunarResult['yearName']} {$lunarResult['shengXiao']} {$lunarResult['monthName']}{$lunarResult['dayName']}\n";
// 农历转公历
$solarResult = LunarCalendar::lunarToSolar(2024, 1, 15);
echo "公历日期:{$solarResult['year']}-{$solarResult['month']}-{$solarResult['day']}\n";
方法3:使用简单的算法实现
如果不希望依赖第三方库,这里有一个更轻量级的实现:
<?php
class SimpleLunar {
private static $lunarInfo = [
// 1900-2100年农历数据(每个数据表示该年的农历信息)
0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2
];
public static function solarToLunar($year, $month, $day) {
// 获取年份数据
$yearData = self::$lunarInfo[$year - 1900];
// 计算正月初一对应的公历日期
$baseDate = strtotime("1900-01-31");
$targetDate = strtotime("$year-$month-$day");
$offset = floor(($targetDate - $baseDate) / 86400);
// 计算农历年月日
$lunarYear = 1900;
$lunarMonth = 1;
$lunarDay = 1;
while ($offset > 0) {
$yearDays = self::getLunarYearDays($lunarYear);
if ($offset < $yearDays) {
break;
}
$offset -= $yearDays;
$lunarYear++;
}
// 获取闰月信息
$leapMonth = self::getLeapMonth($lunarYear);
for ($i = 1; $i <= 12; $i++) {
$monthDays = self::getLunarMonthDays($lunarYear, $i);
if ($offset < $monthDays) {
$lunarMonth = $i;
$lunarDay = $offset + 1;
break;
}
$offset -= $monthDays;
}
return [
'year' => $lunarYear,
'month' => $lunarMonth,
'day' => $lunarDay,
'isLeap' => false
];
}
private static function getLunarYearDays($year) {
$sum = 348;
for ($i = 0x8000; $i > 0x8; $i >>= 1) {
$sum += (self::$lunarInfo[$year - 1900] & $i) ? 1 : 0;
}
return $sum + self::getLeapMonthDays($year);
}
private static function getLeapMonthDays($year) {
if (self::getLeapMonth($year)) {
return (self::$lunarInfo[$year - 1900] & 0x10000) ? 30 : 29;
}
return 0;
}
private static function getLeapMonth($year) {
return self::$lunarInfo[$year - 1900] & 0xf;
}
private static function getLunarMonthDays($year, $month) {
return (self::$lunarInfo[$year - 1900] & (0x10000 >> $month)) ? 30 : 29;
}
}
推荐方案
- 生产环境:推荐使用
overtrue/lunar库,功能完整且经过测试 - 简单项目:可以使用自定义实现
- 性能要求高:考虑使用缓存或数据库存储农历数据
选择哪种方案取决于你的具体需求,如果需要完整的农历功能(如节气、节日等),建议使用成熟的第三方库。