资讯专栏INFORMATION COLUMN

阴历阳历的相互转换(支持1900~2100年)

geekzhou / 3425人阅读

摘要:背景最近做到一个项目需要阴历与阳历的相互转换网上找了很多资料发现很多都是不准的但是给了我参考价值算法借用百度百科的阳历太阳历又称为阳历,是以地球绕太阳公转的运动周期为基础而制定的历法。

背景

最近做到一个项目, 需要阴历与阳历的相互转换, 网上找了很多资料, 发现很多都是不准的, 但是给了我参考价值

算法

借用百度百科的 :

阳历

太阳历又称为阳历,是以地球绕太阳公转的运动周期为基础而制定的历法。
太阳历的历年近似等于回归年,一年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日为宰牲节日。该历的星期,使用七曜(日、月、火、水、木、金、土)记日的周日法。每周逢金曜为“主麻日”,穆斯林在这一天举行“聚礼”。

思路

借用: https://blog.csdn.net/hsd2012... 这里的解释: 要想计算给定的时间对于的农历是哪一天,我们需要找一个参考时间,然后以该参考时间计算以后的时间。首先计算当前时间与参考时间相差的天数,然后通过求出农历每年的天数,计算当前时间对应的是哪一年的第几天,最后计算出属于那个月的哪一个日期。

计算生肖属相

目前的做法是 阴历年份 - 1900 + 36 然后除以 12 取余数, 得出生肖属相的序号, 至于为啥这么计算, 我是没搞懂, 网上也搜了很多也没说清楚, 这里参考文章: https://juejin.im/entry/59904...

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

Z = 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 的实现完整代码
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...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30847.html

相关文章

  • 阴历阳历相互转换支持1900~2100

    摘要:背景最近做到一个项目需要阴历与阳历的相互转换网上找了很多资料发现很多都是不准的但是给了我参考价值算法借用百度百科的阳历太阳历又称为阳历,是以地球绕太阳公转的运动周期为基础而制定的历法。 背景 最近做到一个项目, 需要阴历与阳历的相互转换, 网上找了很多资料, 发现很多都是不准的, 但是给了我参考价值 算法 借用百度百科的 : 阳历 太阳历又称为阳历,是以地球绕太阳公转的运动周期为基础而...

    whjin 评论0 收藏0
  • 阴历阳历相互转换支持1900~2100

    摘要:背景最近做到一个项目需要阴历与阳历的相互转换网上找了很多资料发现很多都是不准的但是给了我参考价值算法借用百度百科的阳历太阳历又称为阳历,是以地球绕太阳公转的运动周期为基础而制定的历法。 背景 最近做到一个项目, 需要阴历与阳历的相互转换, 网上找了很多资料, 发现很多都是不准的, 但是给了我参考价值 算法 借用百度百科的 : 阳历 太阳历又称为阳历,是以地球绕太阳公转的运动周期为基础而...

    JinB 评论0 收藏0
  • 4月份前端资源分享

    摘要:更多资源请文章转自月份前端资源分享关于的思考一款有趣的动画效果跨站资源共享之二最流行的编程语言能做什么到底什么是闭包的第三个参数跨域资源共享详解阮一峰前端要给力之语句在中的值周爱民中国第二届视频花絮编码规范前端工程师手册奇舞周刊被忽视的 更多资源请Star:https://github.com/maidishike... 文章转自:https://github.com/jsfron...

    jsdt 评论0 收藏0
  • 利用JavaScript实现ISO周日历

      知识普及  阳历:就是以太阳来计算日期的一类历法;  阴历:根据月亮周期制定出的历法,由阴转晴,再由晴转阴为一个月,换算下来合29天12个小时44分零二秒八,接近30天。  公历:属阳历的一种,我国现在使用的就是公历;  农历:我国的农历是一种阴阳合历,用来指导农业十分方便。  总结来说公历属于阳历,但是阳历并不一定是公历。农历不是阴历,而是阴阳历,是以阴历为主,阳历为辅。  公历:用阿拉伯数...

    3403771864 评论0 收藏0
  • 深入浅出排序学习:写给程序员算法系统开发实践

    引言 我们正处在一个知识爆炸的时代,伴随着信息量的剧增和人工智能的蓬勃发展,互联网公司越发具有强烈的个性化、智能化信息展示的需求。而信息展示个性化的典型应用主要包括搜索列表、推荐列表、广告展示等等。 很多人不知道的是,看似简单的个性化信息展示背后,涉及大量的数据、算法以及工程架构技术,这些足以让大部分互联网公司望而却步。究其根本原因,个性化信息展示背后的技术是排序学习问题(Learning to ...

    caige 评论0 收藏0

发表评论

0条评论

geekzhou

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<