陰曆陽曆的相互轉換(支援1900~2100年)

來源:互聯網
上載者:User

背景

最近做到一個項目, 需要陰曆與陽曆的相互轉換, 網上找了很多資料, 發現很多都是不準的, 但是給了我參考價值

演算法

借用百度百科的 :

陽曆

太陽曆又稱為陽曆,是以地球繞太陽公轉的運動周期為基礎而制定的曆法。
太陽曆的曆年近似等於迴歸年,一年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 - 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 $solarDay     * @param $solarDay     * @return string     */    private function dayGanZhi($solarYear, $solarDay, $solarDay)    {        return "";    }    // TODO 尚未實現    /**     * 計算月的天干地支     * @param $solarYear     * @param $solarDay     * @param $solarDay     * @return string     */    private function mothGanZhi($solarYear, $solarDay, $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...

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.