天天看點

陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明

結合上次做的月曆,提前粘貼一下效果圖

目前月份

陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明

下一個月

陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明

相關概念

陽曆,有很強的規律性。每年12個月,1、3、5、7、8、10、12月都為31天;平年2月份28天,潤年2月份29天,其餘的月30天。

陰曆,卻沒有這些規律可循。平年十二個月,大月三十天,小月二十九天,全年354天或355天(一年中哪個月大,哪個月小,年年不同)。由于每年的天數比太陽年約差十一天,是以在十九年裡設定七個閏月,有閏月的年份全年383天或384天。又根據太陽的位置,把一個太陽年分成二十四個節氣,以利于農業種植等活動。紀年用天幹地支搭配,六十年周而複始。這種曆法相傳創始于夏代,是以又稱為夏曆。也叫舊曆。 是以,推算陰曆就沒有一個統一的算法。

公曆=陽曆 是世界通用的日期也就是我們平常的日期

農曆=陰曆 是我國古代用來農耕的日期,也就是月曆下面的小字所表示的日期

1,陽曆–以地球繞太陽一周為一年所定出的曆法.

2,陰曆–以太陰(月亮)繞地球為一個月,12個月為一年(閏年為13個月)所定出來的曆法.(以閏月調節年之四時).

思路解析

總體思路

要想計算給定的時間對于的農曆是哪一天,我們需要找一個參考時間,然後以該參考時間計算以後的時間。首先計算目前時間與參考時間相差的天數,然後通過求出農曆每年的天數,計算目前時間對應的是哪一年的第幾天,最後計算出屬于那個月的哪一個日期。

怎樣計算生肖

因為共計12個生肖,”鼠”,”牛”,”虎”,”兔”,”龍”,”蛇”,”馬”,”羊”,”猴”,”雞”,”狗”,”豬”,那麼每12年一個輪回。目前2016年是猴年,如果從0開始計數,猴排在第8個,且2016 % 12= 0===》(2016 % 12 +8) %12 ==8

于是可以得出公式(y代表陽曆年份)

(y % 12 +8) %12

以上公式可以簡化

(y % 12 -4) %12

再次簡化

(y -4) %12

怎樣計算天幹地支

中國古代的一種紀年法。即以甲、乙、丙、丁、戊、己、庚、辛、壬、癸為十幹,子、醜、寅、卯、辰、巳、午、未、申、酉、戌、亥為十二支,把幹、支順序配合。如甲子、乙醜…甲戌等,經過六十年又回到甲子。周而複始,循環不已。

且查月曆可以得知1900 年是===>庚子年 丁醜月 甲辰日

假設天幹地支序号都從0開始,庚排在6号位,子排在0号位

針對年而言

(1900 -4) % 10 =6 ===>庚

(1900-4) % 12 =4 ===>子

于是總結公式如下(農曆年y)

(y-4) % 10 ==>天幹對應的位置編号

(y-4) % 12 ==>地支對應的位置編号

同理,可以求出對應的月和,隻是針對月和日,需要考慮的是閏月問題,不再細說。

具體實作

所有代碼如下:

<?php
/**
*author:dequan
*date:2016-06-20
*原文位址:http://blog.csdn.net/hsd2012/article/details/51701640
*/
class Calendar{
    private $animals=array("鼠","牛","虎","兔","龍","蛇","馬","羊","猴","雞","狗","豬");
    private $curData=null;//目前陽曆時間
    private $ylYeal=;
    private $ylMonth=;
    private $yldate=;
    private $ylDays=; //目前日期是農曆年的第多少天
    private $leap=;//代表潤哪一個月
    private $leapDays=;//代表閏月的天數
    private $difmonth=;//目前時間距離參考時間相差多少月
    private $difDay=;//目前時間距離參考時間相差多少天
    private $tianGan=array("甲","乙","丙","丁","戊","己","庚","辛","壬","癸");
    private $diZhi=array("子","醜","寅","卯","辰","巳","午","未","申","酉","戌","亥");
    private $dataInfo=array(,,,,,,,,,,//1900-1909
,,,,,,,,,,//1910-1919
,,,,,,,,,,//1920-1929
,,,,,,,,,,//1930-1939
,,,,,,,,,,//1940-1949
,,,,,,,,,,//1950-1959
,,,,,,,,,,//1960-1969
,,,,,,,,,,//1970-1979
,,,,,,,,,,//1980-1989
,,,,,,,,,,//1990-1999
,,,,,,,,,,//2000-2009
,,,,,,,,,,//2010-2019
,,,,,,,,,,//2020-2029
,,,,,,,,,,//2030-2039
,,,,,,,,,,//2040-2049
/**Add By [email protected]**/
,,,,,,, ,,,//2050-2059
,,,,,,,,,,//2060-2069
,,,,,,,,,,//2070-2079
,,,,,,,,,,//2080-2089
,,,,,,,,,,//2090-2099
);
    public function __construct($curData=null){
        if(!empty($curData)){
            $this->curData=$curData;
        }else{
            $this->curData=date('Y-n-j');
        }
        $this->init();
    }

    public function init(){
        $basedate='1900-1-31';//參照日期
        $timezone='PRC';
        $datetime= new DateTime($basedate, new DateTimeZone($timezone)); 
        $curTime=new DateTime($this->curData, new DateTimeZone($timezone));
        $offset   = ($curTime->format('U') - $datetime->format('U'))/; //相差的天數
        $offset=ceil($offset);
        $this->difDay=$offset;
        $offset+=;//隻能使用ceil,不能使用intval或者是floor,因為1900-1-31為正月初一,故需要加1
        for($i=; $i< && $offset>; $i++){
          $temp = $this->getYearDays($i); //計算i年有多少天
          $offset -= $temp ;
          $this->difmonth+=;
          //判斷該年否存在閏月
          if($this->leapMonth($i)>){
            $this->difmonth+=;
          } 
        }

        if($offset<){
          $offset += $temp;
          $i--;
          $this->difmonth-=; 
        }
        if($this->leapMonth($i)>){
            $this->difmonth-=;
        } 
        $this->ylDays=$offset;
        //此時$offset代表是農曆該年的第多少天
        $this->ylYeal=$i;//農曆哪一年
        //計算月份,依次減去1~12月份的天數,直到offset小于下個月的天數
        $curMonthDays=$this->monthDays($this->ylYeal,);
        //判斷是否該年是否存在閏月以及閏月的天數
        $this->leap=$this->leapMonth($this->ylYeal);
        if($this->leap !=){
            $this->leapDays=$this->leapDays($this->ylYeal);
        }

        for($i=;$i< && $curMonthDays<$offset;$curMonthDays=$this->monthDays($this->ylYeal,++$i)){     
            if($this->leap == $i){ //閏月
                if($offset>$this->leapDays){
                    --$i;
                    $offset-=$this->leapDays;
                    $this->difmonth+=;
                }else{
                    break;
                }
            }else{
                $offset-=$curMonthDays;
                $this->difmonth+=;
            }
        }

        $this->ylMonth=$i;
        $this->yldate=$offset;
    }
    /**
    *計算農曆y年有多少天
    **/
    public function getYearDays($y){ 
       $sum = ;//12*29=348,不考慮小月的情況下
       for($i=; $i>=; $i>>=){ 
            $sum += ($this->dataInfo[$y-] & $i)? : ;
       } 
       return($sum+$this->leapDays($y));
    }
    /**
    *擷取某一年閏月的天數
    **/
    public function leapDays($y){
        if($this->leapMonth($y)){
            return(($this->dataInfo[$y-] & )? : );

        } else {
            return();
        }           
    }
    /**
    *計算哪一月為閏月
    */
    public  function leapMonth($y){ 
        return ($this->dataInfo[$y-] & );
    }
    /**
    *計算農曆y年m月有多少天
    */
    public function monthDays($y,$m){
        return (($this->dataInfo[$y-] & (>>$m))? :  );
    }

    public function getLyTime(){
        $tmp=array('初','一','二','三','四','五','六','七','八','九','十','廿');
        $dateStr='';
        if($this->ylMonth>){     
            $m2=intval($this->ylMonth -); //十位
            $dateStr='十'.$tmp[$m2].'月';
        }elseif($this->ylMonth==){
            $dateStr='正月';
        }else{
            $dateStr=$tmp[$this->ylMonth].'月';
        }

        if($this->yldate <){
            $dateStr.='初'.$tmp[$this->yldate];
        }else{
            $m1=intval($this->yldate / );
            if( $m1 !=){
                $dateStr.=($m1==)?'十':'廿';
                $m2=$this->yldate % ;
                if($m2==){
                    $dateStr.='十';
                }else{
                    $dateStr.=$tmp[$m2];
                }
            }else{
                $dateStr.='三十';
            }
        }
        return $dateStr;
    }
    /**
    *擷取該年對于的天幹地支年
    **/
    public function getYGanZhi(){
        $gan=$this->tianGan[($this->ylYeal-) % ];
        $zhi=$this->diZhi[($this->ylYeal-) % ];
        return $gan.$zhi.'年';
    }
    /**
    *擷取該年對于的天幹地支月
    **/
    public function getMGanZhi(){       
        $gan=$this->tianGan[($this->difmonth+) % ];
        $zhi=$this->diZhi[($this->difmonth+) % ];
        return $gan.$zhi.'月';
    }
    /**
    *擷取該年對于的天幹地支日
    **/
    public function getDGanZhi(){       
        $gan=$this->tianGan[$this->difDay % ];
        $zhi=$this->diZhi[($this->difDay+) % ];
        return $gan.$zhi.'日';
    }
}
$c=new Calendar();
$time=$c->getLyTime();
trace(date('Y-n-j').'對應的農曆時間:'.$time);

$c=new Calendar('2014-10-1');
$time=$c->getLyTime();
trace(date('Y-n-j').'對應的農曆時間:'.$time);

function trace($info=''){
    echo '<pre>';
    print_r($info);
    echo '</pre>';
}

           

效果圖如下

陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明

對比百度中月曆

陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明

測試okey。

********暫時沒有添加節假日以及星座等,後期會考慮添加更***

細節相關說明

農曆十六進制資料解析

1900年的資料是: 0x04bd8

二進制:0000 0100 1011 1101 1000

(如果不想自己推算,在JS裡面可以通過

console.log(0x04bd8.toString(2));

來轉換)

對應這些二進制描述如下:

前4位,在這一年是潤年時才有意義,它代表這年潤月的大小月,1潤大月30天,0潤小月29天。

中間12位,每位代表一個月,為1則為大月30天,為0則為小月29天

最後4位,即8,代表這一年的潤月月份,為0則不潤。首4位要與末4位搭配使用。

二進制 解析
0000 閏月為小月
0100 1011 1101 1~12個月,每個月包含的天數
1000 代表哪個月為閏月,為0的時候,代表沒有閏月

故由以上資訊可知1900年閏8月,且閏月為小月,從1月到12月的天數依次為:29、30 、29、29、30、29、 30、30、29(閏月)、30、30、29、30。

通過查詢百度月曆,也可以驗證上面我推算

陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明
陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明

1900~2100年

,x04ae0,,,,,,,,,//1900-1909
,,,,,,,,,,//1910-1919
,,,,,,,,,,//1920-1929
,,,,,,,,,,//1930-1939
,,,,,,,,,,//1940-1949
,,,,,,,,,,//1950-1959
,,,,,,,,,,//1960-1969
,,,,,,,,,,//1970-1979
,,,,,,,,,,//1980-1989
,,,,,,,,,,//1990-1999
,,,,,,,,,,//2000-2009
,,,,,,,,,,//2010-2019
,,,,,,,,,,//2020-2029
,,,,,,,,,,//2030-2039
,,,,,,,,,,//2040-2049
,,,,,,, ,,,//2050-2059
,,,,,,,,,,//2060-2069
,,,,,,,,,,//2070-2079
,,,,,,,,,,//2080-2089
,,,,,,,,,,//2090-2099
////2100
           

為什麼選擇陽曆1900.1.31作為參考點

因為能找到最早的農曆十六進制資料是1900年。且通過查詢,可以得知,陽曆1900.1.31,正好是農曆1900.1.1。這樣便于推算

陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明

怎樣計算農曆y年有多少天

在上面,我們可以由農曆十六進制資料推算,每個月有多少天,以及是否閏月,那麼久很容易推算出某一年有多少天,但是,我們是将其轉為二進制來計算的。如1990 <====>0x04bd8,轉換成二進制為

0000 0100 1011 1100 0100,怎樣才能分别得到其第5位到第16位上的數字呢?這時候,我們可以通過與運算來實作。讓其分别如以下二進制做與運算

0000 1000 0000 0000 0000 ===>0x08000 ==>如果為0,代表第5位上的數字為0,否則為1

0000 0100 0000 0000 0000 ===>0x04000 ==>如果為0,代表第6位上的數字為0,否則為1

0000 0010 0000 0000 0000 ===>0x02000 ==>如果為0,代表第7位上的數字為0,否則為1

********************中間省略********************************

0000 0000 0000 0010 0000 ===>0x00020 ==>如果為0,代表第15位上的數字為0,否則為1

0000 0000 0000 0001 0000 ===>0x00010 ==>如果為0,代表第16位上的數字為0,否則為1

0x08000 ====>0x00010

于是可以使用循環語句如下

for($i=x8000; $i>=x10; $i>>=){
    $curNum=x04bd8 && $i ?  :;
}
           

這樣計算農曆y年有多少天程式就比較容易實作

/**
    *計算農曆y年有多少天
    **/
    public function getYearDays($y){ 
       $sum = ;//12*29=348,不考慮小月的情況下
       for($i=; $i>=; $i>>=){ 
            $sum += ($this->dataInfo[$y-] & $i)? : ;
       } 
       return($sum+$this->leapDays($y));
    }
    /**
    *擷取某一年閏月的天數
    **/
    public function leapDays($y){
        if($this->leapMonth($y)){
            return(($this->dataInfo[$y-] & )? : );

        } else {
            return();
        }           
    }
    /**
    *計算哪一月為閏月
    */
    public  function leapMonth($y){ 
        return ($this->dataInfo[$y-] & );
    }
           

PHP 32位怎樣解決時間戳範圍的限制問題

當我們在求一個時間的時間戳的時候,如果時間太靠前或者是太靠後,PHP是不能給出正确的解析,如下

var_dump(strtotime('1900-1-31'));  //傳回false
var_dump(strtotime('2050-3-01'));//傳回false
           

檢視手冊可以看到

陽曆轉換成陰曆PHP實作詳解相關概念思路解析具體實作細節相關說明

對于PHP 64位版本來說,就不存在時間戳範圍的限制了

為了解決時間範圍的限制的問題,PHP5.2及以上版本提供了DateTime類。

$c1=new DateTime('1900-1-31', new DateTimeZone('PRC'));
$c2=new DateTime('2111-01-01',new DateTimeZone('PRC'));

var_dump($c1->format('U')); //-2206425600
var_dump($c2->format('U'));//4449484800