最近做到一个项目, 须要农历与阳历的相互转换, 网上找了很多资料, 创造很多都是不准的, 但是给了我参考代价

算法

借用百度百科的 :

阳历

太阳历又称为阳历,因此地球绕太阳公转的运动周期为根本而制订的历法。

php阳历查询农历PHP实现阴历阳历的互相转换支撑19002100年 NoSQL

太阳历的历年近似即是回归年,一年12个月,这个“月”,实际上与朔望月无关。

阳历的月份、日期都与太阳在黄道上的位置较好地符合,根据阳历的日期,在一年中可以明显看出四季寒暖变革的情形;但在每个月份中,看不出玉轮的朔、望、两弦。

如今天下通畅的公历便是一种阳历,平年365天,闰年366天,每四年一闰,每满百年少闰一次,到第四百年再闰,即每四百年中有97个闰年。
公历的历年均匀长度与回归年只有26秒之差,要累积3300年才差一日。

农历

希吉来历系太农历,其打算方法是: 以太阴圆缺一周为一月,历时29日12小时44分2.8秒,太阴圆缺十二周为一年,历时354日8小时48分33.6秒。
每一年的12个月中,6个单数月份(即1、3、5、7、9、11月)为“大建”,每月为30天; 6个双数月份(2、4、6、8、10、12月)为“小建”,每月为29天;在逢闰之年,将12月改大月为30天。
该历以30年为一周期,每一周期里的第2、5、7、10、13、16、18、21、24、26、29年,共11年为闰年, 不设置闰月,而在12月末置一闰日,闰年为355日,另19年为平年,每年354日。
故均匀每年为354日8小时48分。
按该历整年实际天数打算,比回归年约少10日21小时1分,积2.7回归年相差一月,积32.6回归年相差一年。
该历对昼夜的打算,以日落为一天之始,到越日日落为一日,常日称为夜行前,即黑夜在前,日间在后,构成一天。
希吉来历每年9月(莱麦丹)为伊斯兰教斋戒之月, 对这个月的起讫除了打算之外,还要由不雅观察月牙是否涌现来决定。
即在8月29日这天进行不雅观测,如见月牙,第二日即为9月1日,黎明前开始斋戒,8月仍为小建; 如不见月牙,第三日则为9月1日,8月即变为“大建”。
到了9月29日傍晚,也须要看月,如见月牙,第二天便是10月1日,即为开斋节日,使9月变成“小建”;如未见月牙,斋戒必须再延一天,9月即为“大建”。
12月(祖勒·希哲)上旬为朝觐日期,12月10日为宰牲节日。
该历的星期,利用七曜(日、月、火、水、木、金、土)记日的周日法。
每周逢金曜为“主麻日”,穆斯林在这一天举行“聚礼”。

思路

要想打算给定的韶光对付的农历是哪一天,我们须要找一个参考韶光,然后以该参考韶光打算往后的韶光。
首先打算当前韶光与参考韶光相差的天数,然后通过求出农历每年的天数,打算当前韶光对应的是哪一年的第几天,末了打算出属于那个月的哪一个日期。

打算生肖属相

目前的做法是 阴历年份 - 1900 + 36 然后除以 12 取余数, 得出生肖属相的序号。

private function yearShengXiao($lunarYear){ // TODO 至于为什么这样弄, 我也没搞清楚 return self::SHENG_XIAO[($lunarYear - self::MIN_YEAR + 36) % 12]; // 年的属相}年的干支算法

网上搜到的做法是用公元年来打算, 但是不对, 然后我换成阴历年居然就跟百度的日历能对上了, 这个我也没弄清楚, 但是能算出来了, 公式: 年数先减三,除10余数是天干,基数改用12除,余数便是地支年 (如果余数为 0 ,则取最大序号)

private function yearGanZhi($lunarYear){ // 年数先减三,除10余数是天干,基数改用12除,余数便是地支年 (如果余数为 0 ,则取最大序号) $yJiShu = $lunarYear - 3; $yTianGan = ($yJiShu % 10 == 0) ? 10 : $yJiShu % 10; $yDiZhi = ($yJiShu % 12 == 0) ? 10 : $yJiShu % 12; $yGanZhi = self::TIAN_GAN[$yTianGan - 1] . self::DI_ZHI[$yDiZhi - 1]; // // 由于是从 0 开始,这里再减一 return $yGanZhi;}月的干支算法

网上搜索了, 没找到好的实现办法, 麻烦知道的在这里说一下,

日的干支算法

网上搜到的:

G = 4C + [C / 4] + 5y + [y / 4] + [3 (M + 1) / 5] + d - 3Z = 8C + [C / 4] + 5y + [y / 4] + [3 (M + 1) / 5] + d + 7 + i个中C 是世纪数减一,y 是年份后两位,M 是月份,d 这天数。
1月和2月按上一年的13月和14月来算。
奇数月i=0,偶数月i=6。
G 除以10的余数是天干,Z 除以12的余数是地支。

但是不对, 麻烦有懂也奉告下

PHP 的实现完全代码

<?php/ 阳历和新历的转换 /class SolarLunar { // 最小年 const MIN_YEAR = 1900; // 最大年 const MAX_YEAR = 2100; // 开始的日期 const START_DATE_STR = \"大众1900-01-30\公众; const CHINESE_NUMBER = [\"大众一\"大众, \公众二\"大众, \公众三\公众, \公众四\公众, \"大众五\公众, \"大众六\公众, \公众七\"大众, \"大众八\公众, \公众九\"大众, \"大众十\"大众, \公众十一\"大众, \"大众十二\"大众]; const CHINESE_NUMBER_SPECIAL = [\"大众正\"大众, \"大众二\"大众, \"大众三\"大众, \"大众四\"大众, \公众五\"大众, \"大众六\公众, \公众七\"大众, \"大众八\"大众, \"大众九\"大众, \"大众十\"大众, \"大众冬\公众, \"大众腊\"大众]; const TIAN_GAN = [\"大众甲\"大众, \"大众乙\公众, \"大众丙\"大众, \"大众丁\公众, \公众戊\"大众, \"大众己\公众, \公众庚\"大众, \"大众辛\"大众, \"大众壬\"大众, \"大众癸\"大众]; const DI_ZHI = [\"大众子\公众, \"大众丑\"大众, \"大众寅\"大众, \"大众卯\"大众, \"大众辰\"大众, \"大众巳\"大众, \"大众午\"大众, \"大众未\"大众, \"大众申\"大众, \公众酉\公众, \"大众戌\公众, \公众亥\"大众]; const SHENG_XIAO = [\公众鼠\"大众, \"大众牛\"大众, \"大众虎\"大众, \"大众兔\"大众, \"大众龙\"大众, \公众蛇\"大众, \公众马\公众, \"大众羊\公众, \"大众猴\"大众, \"大众鸡\"大众, \"大众狗\"大众, \公众猪\公众]; // 阴历年份的数据 const LUNAR_INFO = [ 0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,//1900-1909 0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,//1910-1919 0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,//1920-1929 0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,//1930-1939 0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,//1940-1949 0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,//1950-1959 0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,//1960-1969 0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,//1970-1979 0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,//1980-1989 0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,//1990-1999 0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,//2000-2009 0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,//2010-2019 0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,//2020-2029 0x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,//2030-2039 0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,//2040-2049 0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50, 0x06b20,0x1a6c4,0x0aae0,//2050-2059 0x0a2e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,//2060-2069 0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,//2070-2079 0x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,//2080-2089 0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252,//2090-2099 0x0d520]; / 农历转阳历 @param string $date @param bool $leapMonthFlag @return string / public function lunarToSolar($date = \公众\"大众, $leapMonthFlag = false) { try { $dateTime = new \DateTime($date); } catch (\Exception $exception) { return \"大众\"大众; } $lunarYear = $dateTime->format(\公众Y\"大众); $lunarMonth = $dateTime->format(\公众n\"大众); $lunarDay = $dateTime->format(\"大众j\"大众); // 检讨是否合法 if (!$this->checkLunarDate($lunarYear, $lunarMonth, $lunarDay, $leapMonthFlag)) { return \公众\公众; } $offset = 0; for ($i = self::MIN_YEAR; $i < $lunarYear; $i++) { $yearDaysCount = $this->getYearDays($i); // 求农历某年天数 $offset += $yearDaysCount; } //打算该年闰几月 $leapMonth = $this->getLeapMonth($lunarYear); if ($leapMonthFlag && $leapMonth != $lunarMonth) { // 您输入的闰月标志有误 return \公众\公众; } if ($leapMonth == 0 || ($lunarMonth < $leapMonth) || ($lunarMonth == $leapMonth && !$leapMonthFlag)) { for ($i = 1; $i < $lunarMonth; $i++) { $tempMonthDaysCount = $this->getMonthDays($lunarYear, $i); $offset += $tempMonthDaysCount; } // 检讨日期是否大于最大天 if ($lunarDay > $this->getMonthDays($lunarYear, $lunarMonth)) { // 不合法的农历日期 return \"大众\"大众; } $offset += intval($lunarDay); // 加上当月的天数 } else { //当年有闰月,且月份晚于或即是闰月 for ($i = 1; $i < $lunarMonth; $i++) { $tempMonthDaysCount = $this->getMonthDays($lunarYear, $i); $offset += $tempMonthDaysCount; } if ($lunarMonth > $leapMonth) { $temp = $this->getLeapMonthDays($lunarYear); // 打算闰月天数 $offset += $temp; // 加上闰月天数 if ($lunarDay > $this->getMonthDays($lunarYear, $lunarMonth)) { // 不合法的农历日期 return \公众\"大众; } $offset += intval($lunarDay); } else { // 如果须要打算的是闰月,则应首先加上与闰月对应的普通月的天数 // 打算月为闰月 $temp = $this->getMonthDays($lunarYear, $lunarMonth); // 打算非闰月天数 $offset += $temp; if ($lunarDay > $this->getLeapMonthDays($lunarYear)) { // 不合法的农历日期 return \"大众\"大众; } $offset += intval($lunarDay); } } try { $newDateTime = new \DateTime(self::START_DATE_STR); } catch (\Exception $exception) { return \"大众\公众; } $sumH = 24 $offset; $newDateTime->add(new \DateInterval('PT' . $sumH . 'H')); return $newDateTime->format(\公众Y-m-d\"大众); } / 把阳历转换为中文的农历 @param string $date @return string / public function solarToChineseLunar($date) { $dateTime = new \DateTime($date); list($lunarYear, $lunarMonth, $lunarDay, $leapMonth, $leapMonthFlag) = $this->calculateLunar($dateTime); $result = \公众\"大众; if($leapMonthFlag && $lunarMonth == $leapMonth){ $result .= \"大众闰\"大众; } $solarYear = (int)$dateTime->format(\"大众Y\公众); $solarMoth = (int)$dateTime->format(\公众n\"大众); $solarDay = (int)$dateTime->format(\公众j\公众); $result .= self::CHINESE_NUMBER_SPECIAL[$lunarMonth-1] . \公众月\公众; $result .= $this->chineseDayString($lunarDay) . \"大众日\"大众; // 年的天干地支 $yGanZhi = $this->yearGanZhi($lunarYear); // 生肖属相 $yShengXiao = $this->yearShengXiao($lunarYear); $result .= \"大众,\公众 . $yGanZhi . \"大众年 [\"大众 . $yShengXiao . '年]'; // 月的天干地支 $mTianGanDiZhi = $this->mothGanZhi($solarYear, $solarMoth, $solarDay); $result .= \"大众,\"大众 . $mTianGanDiZhi . '月'; // 日的天干地支 $dTianGanDiZhi = $this->dayGanZhi($solarYear, $solarMoth, $solarDay); $result .= \公众,\公众 . $dTianGanDiZhi . '日'; return $result; } / 阳历转换为大略的中文农历 @param string $date @return string / public function solarToSimpleLunar($date) { $dateTime = new \DateTime($date); list($lunarYear, $lunarMonth, $lunarDay, $leapMonth, $leapMonthFlag) = $this->calculateLunar($dateTime); $result = $lunarYear . \"大众年\"大众; if($leapMonthFlag && $lunarMonth == $leapMonth) { $result .= \公众闰\"大众; } if($lunarMonth < 10){ $result .= \公众0\公众 . $lunarMonth . \"大众月\"大众; } else { $result .= $lunarMonth . \"大众月\"大众; } if($lunarDay < 10){ $result .= \"大众0\公众 . $lunarDay . \"大众日\"大众; } else { $result .= $lunarDay . \"大众日\"大众; } return $result; } / 阳历转为正常的农历 @param string $date @param bool $isLeapMonth 是否是闰月 @return string / public function solarToLunar($date, &$isLeapMonth = false) { $dateTime = new \DateTime($date); list($lunarYear, $lunarMonth, $lunarDay, $leapMonth, $leapMonthFlag) = $this->calculateLunar($dateTime); $result = $lunarYear . \公众-\公众; if($lunarMonth < 10){ $result .= \"大众0\"大众 . $lunarMonth . \公众-\"大众; } else { $result .= $lunarMonth . \"大众-\"大众; } if($lunarDay < 10){ $result .= \公众0\"大众 . $lunarDay; } else { $result .= $lunarDay; } $isLeapMonth = false; if($leapMonthFlag && $lunarMonth == $leapMonth) { $isLeapMonth = true; } return $result; } / 打算当前日期的农历 @param \DateTime $dateTime @return array / private function calculateLunar($dateTime) { $i = 0; $temp = 0; $leapMonthFlag = false; $isLeapYear = false; $startDate = new \DateTime(self::START_DATE_STR); $offset = $this->daysBwteen($dateTime, $startDate); for($i = self::MIN_YEAR; $i < self::MAX_YEAR; $i++){ $temp = $this->getYearDays($i); //求当年农历年天数 if($offset - $temp < 1){ break; } else { $offset -= $temp; } } $lunarYear = $i; $leapMonth = $this->getLeapMonth($lunarYear); //打算该年闰哪个月 //设定当年是否有闰月 if($leapMonth > 0 ){ $isLeapYear = true; } else { $isLeapYear = false; } for($i = 1; $i <= 12; $i++){ if($i == $leapMonth + 1 && $isLeapYear){ $temp = $this->getLeapMonthDays($lunarYear); $isLeapYear = false; $leapMonthFlag = true; $i--; } else { $temp = $this->getMonthDays($lunarYear, $i); } $offset -= $temp; if($offset <= 0){ break; } } $offset += $temp; $lunarMonth = $i; $lunarDay = $offset; return [$lunarYear, $lunarMonth, $lunarDay, $leapMonth, $leapMonthFlag]; } / 检讨农历是否合法 @param int $lunarYear @param int $lunarMonth @param int $lunarDay @param bool $leapMonthFlag @return bool @throws Exception / private function checkLunarDate($lunarYear, $lunarMonth, $lunarDay, $leapMonthFlag = false) { if ($lunarYear < self::MIN_YEAR || $lunarYear > self::MAX_YEAR) { // 造孽农历年份 return false; } if ($lunarMonth < 1 || $lunarMonth > 12) { // 造孽农历月份 return false; } if ($lunarDay < 1 || $lunarDay > 30) { // 中国的月最多30天 // 造孽农历天数 return false; } $leap = $this->getLeapMonth($lunarYear); // 打算该年该当闰哪个月 if ($leapMonthFlag == true && $lunarMonth != $leap) { // 造孽闰月 return false; } return true; } / 打算该月总天数 @param int $year @param int $month @return int / private function getMonthDays($year, $month) { if ($month > 31 || $month < 0) { // error month return 0; } // 0X0FFFF[0000 {1111 1111 1111} 1111]中间12位代表12个月,1为大月,0为小月 $bit = 1 << (16 - $month); if (((self::LUNAR_INFO[$year - 1900] & 0x0FFFF) & $bit) == 0) { return 29; } return 30; } / 打算阴历年的总天数 @param int $year @return int / private function getYearDays($year) { $sum = 29 12; for ($i = 0x8000; $i >= 0x8; $i >>= 1) { if ((self::LUNAR_INFO[$year - 1900] & 0xfff0 & $i) != 0) { $sum++; } } return $sum + $this->getLeapMonthDays($year); } / 打算阴历年闰月多少天 @param int $year @return int / private function getLeapMonthDays($year) { if ($this->getLeapMonth($year) != 0) { if ((self::LUNAR_INFO[$year - 1900] & 0xf0000) == 0) { return 29; } return 30; } return 0; } / 打算阴历年闰哪个月 1-12 , 没闰传回 0 @param int $year @return int / private function getLeapMonth($year) { return (int)(self::LUNAR_INFO[$year - 1900] & 0xf); } / 打算差的天数 @param \DateTime $date @param \DateTime $startDate @return int / private function daysBwteen($date, $startDate) { $subValue = floatval($date->getTimestamp() - $startDate->getTimestamp()) / 86400.0 + 0.5; return intval($subValue); } / 打算年天干地支 @param $lunarYear @return string / private function yearGanZhi($lunarYear) { // 年数先减三,除10余数是天干,基数改用12除,余数便是地支年 (如果余数为 0 ,则取最大序号) $yJiShu = $lunarYear - 3; $yTianGan = ($yJiShu % 10 == 0) ? 10 : $yJiShu % 10; $yDiZhi = ($yJiShu % 12 == 0) ? 10 : $yJiShu % 12; $yGanZhi = self::TIAN_GAN[$yTianGan - 1] . self::DI_ZHI[$yDiZhi - 1]; // // 由于是从 0 开始,这里再减一 return $yGanZhi; } / 打算年的生肖属相 @param $lunarYear @return mixed / private function yearShengXiao($lunarYear) { // TODO 至于为什么这样弄, 我也没搞清楚 return self::SHENG_XIAO[($lunarYear - self::MIN_YEAR + 36) % 12]; // 年的属相 } // TODO 尚未实现 / 打算日的天干地支 @param $solarYear @param $solarMoth @param $solarDay @return string / private function dayGanZhi($solarYear, $solarMoth, $solarDay) { return \"大众\"大众; } // TODO 尚未实现 / 打算月的天干地支 @param $solarYear @param $solarMoth @param $solarDay @return string / private function mothGanZhi($solarYear, $solarMoth, $solarDay) { return \"大众\"大众; } / 把天转换为中笔墨符 @param int $day @return mixed|string / private function chineseDayString($day) { $chineseTen = [\"大众初\公众, \"大众十\"大众, \"大众廿\"大众, \"大众三\"大众]; $n = 0; if($day % 10 == 0){ $n = 9; } else { $n = $day % 10 - 1; } if($day > 30){ return \"大众\"大众; } if($day == 20){ return \"大众二十\公众; } else if($day == 10){ return \公众初十\"大众; } else { return $chineseTen[$day / 10] . self::CHINESE_NUMBER[$n]; } }}$solarLunar = new SolarLunar();$solarDate = \公众2010-01-05\公众;$new_date = $solarLunar->solarToChineseLunar($solarDate);var_dump($solarDate . \公众 转为农历: \"大众 . $new_date);$new_date = $solarLunar->solarToSimpleLunar($solarDate);var_dump($solarDate . \"大众 转为农历, 中文: \公众 . $new_date);$new_date = $solarLunar->solarToLunar($solarDate, $isLeapMonth);var_dump(\"大众是否是闰月: \公众 . ($isLeapMonth ? \"大众是\"大众 : \"大众否\公众));var_dump($solarDate . \公众 转为农历: \"大众 . $new_date);$lunarDate = \"大众2099-11-25\"大众;$new_date = $solarLunar->lunarToSolar($lunarDate, false);var_dump($lunarDate . \"大众 转新历为: \"大众 . $new_date);

输出:

string(67) \公众2010-01-05 转为农历: 冬月廿一日,己丑年 [牛年],月,日\公众string(50) \公众2010-01-05 转为农历, 中文: 2009年11月21日\"大众string(20) \"大众是否是闰月: 否\"大众string(35) \"大众2010-01-05 转为农历: 2009-11-21\"大众string(35) \"大众2099-11-25 转新历为: 2100-01-05\"大众

如果想要学习互换PHP的朋友,可以关注