# 算法

## 阴历

### 计算生肖属相

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

### 年的干支算法

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 - 3

Z = 8C + [C / 4] + 5y + [y / 4] + [3 * (M + 1) / 5] + d + 7 + i

# 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 = [
0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,//1920-1929
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
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
0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50, 0x06b20,0x1a6c4,0x0aae0,//2050-2059
0x0a2e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,//2060-2069
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"

# GO 实现方式

https://github.com/nosixtools...

#### 你可能感兴趣的

3 条评论
he_xd · 2018年09月10日

https://github.com/overtrue/c... 还有一个类似的包

0

lmxdawn 作者 · 2018年09月10日
0