陽歷,有很強的規律性。每年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*/html' target='_blank'>class Calendar{ private $animals=array('鼠','牛','虎','兔','龍','蛇','馬','羊','猴','雞','狗','豬'); private $curData=null;//當前陽歷時間 private $ylYeal=0; private $ylMonth=0; private $yldate=0; private $ylDays=0; //當前日期是農歷年的第多少天 private $leap=0;//代表潤哪一個月 private $leapDays=0;//代表閏月的天數 private $difmonth=0;//當前時間距離參考時間相差多少月 private $difDay=0;//當前時間距離參考時間相差多少天 private $tianGan=array('甲','乙','丙','丁','戊','己','庚','辛','壬','癸'); private $diZhi=array('子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'); private $dataInfo=array(0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,//1900-19090x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,//1910-19190x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,//1920-19290x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,//1930-19390x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,//1940-19490x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,//1950-19590x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,//1960-19690x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,//1970-19790x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,//1980-19890x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,//1990-19990x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,//2000-20090x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,//2010-20190x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,//2020-20290x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,//2030-20390x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,//2040-2049/**Add By JJonline@JJonline.Cn**/0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50, 0x06b20,0x1a6c4,0x0aae0,//2050-20590x0a2e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,//2060-20690x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,//2070-20790x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,//2080-20890x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252,//2090-20990x0d520); 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'))/86400; //相差的天數 $offset=ceil($offset); $this->difDay=$offset; $offset+=1;//只能使用ceil,不能使用intval或者是floor,因為1900-1-31為正月初一,故需要加1 for($i=1900; $i<2050 && $offset>0; $i++){ $temp = $this->getYearDays($i); //計算i年有多少天 $offset -= $temp ; $this->difmonth+=12; //判斷該年否存在閏月 if($this->leapMonth($i)>0){ $this->difmonth+=1; } } if($offset<0){ $offset += $temp; $i--; $this->difmonth-=12; } if($this->leapMonth($i)>0){ $this->difmonth-=1; } $this->ylDays=$offset; //此時$offset代表是農歷該年的第多少天 $this->ylYeal=$i;//農歷哪一年 //計算月份,依次減去1~12月份的天數,直到offset小于下個月的天數 $curMonthDays=$this->monthDays($this->ylYeal,1); //判斷是否該年是否存在閏月以及閏月的天數 $this->leap=$this->leapMonth($this->ylYeal); if($this->leap !=0){ $this->leapDays=$this->leapDays($this->ylYeal); } for($i=1;$i<13 && $curMonthDays<$offset;$curMonthDays=$this->monthDays($this->ylYeal,++$i)){ if($this->leap == $i){ //閏月 if($offset>$this->leapDays){ --$i; $offset-=$this->leapDays; $this->difmonth+=1; }else{ break; } }else{ $offset-=$curMonthDays; $this->difmonth+=1; } } $this->ylMonth=$i; $this->yldate=$offset; } /** *計算農歷y年有多少天 **/ public function getYearDays($y){ $sum = 348;//12*29=348,不考慮小月的情況下 for($i=0x8000; $i>=0x10; $i>>=1){ $sum += ($this->dataInfo[$y-1900] & $i)? 1: 0; } return($sum+$this->leapDays($y)); } /** *獲取某一年閏月的天數 **/ public function leapDays($y){ if($this->leapMonth($y)){ return(($this->dataInfo[$y-1900] & 0x10000)? 30: 29); } else { return(0); } } /** *計算哪一月為閏月 */ public function leapMonth($y){ return ($this->dataInfo[$y-1900] & 0xf); } /** *計算農歷y年m月有多少天 */ public function monthDays($y,$m){ return (($this->dataInfo[$y-1900] & (0x10000>>$m))? 30: 29 ); } public function getLyTime(){ $tmp=array('初','一','二','三','四','五','六','七','八','九','十','廿'); $dateStr=''; if($this->ylMonth>10){ $m2=intval($this->ylMonth -10); //十位 $dateStr='十'.$tmp[$m2].'月'; }elseif($this->ylMonth==1){ $dateStr='正月'; }else{ $dateStr=$tmp[$this->ylMonth].'月'; } if($this->yldate <11){ $dateStr.='初'.$tmp[$this->yldate]; }else{ $m1=intval($this->yldate / 10); if( $m1 !=3){ $dateStr.=($m1==1)?'十':'廿'; $m2=$this->yldate % 10; if($m2==0){ $dateStr.='十'; }else{ $dateStr.=$tmp[$m2]; } }else{ $dateStr.='三十'; } } return $dateStr; } /** *獲取該年對于的天干地支年 **/ public function getYGanZhi(){ $gan=$this->tianGan[($this->ylYeal-4) % 10]; $zhi=$this->diZhi[($this->ylYeal-4) % 12]; return $gan.$zhi.'年'; } /** *獲取該年對于的天干地支月 **/ public function getMGanZhi(){ $gan=$this->tianGan[($this->difmonth+3) % 10]; $zhi=$this->diZhi[($this->difmonth+1) % 12]; return $gan.$zhi.'月'; } /** *獲取該年對于的天干地支日 **/ public function getDGanZhi(){ $gan=$this->tianGan[$this->difDay % 10]; $zhi=$this->diZhi[($this->difDay+4) % 12]; 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>';}
效果圖如下
對比百度中日歷
測試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位搭配使用。
二進制 | 解析 |
---|---|
故由以上信息可知1900年閏8月,且閏月為小月,從1月到12月的天數依次為:29、30 、29、29、30、29、 30、30、29(閏月)、30、30、29、30。
通過查詢百度日歷,也可以驗證上面我推算
1900~2100年
0x04bd8,x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,//1900-19090x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,//1910-19190x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,//1920-19290x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,//1930-19390x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,//1940-19490x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,//1950-19590x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,//1960-19690x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,//1970-19790x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,//1980-19890x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,//1990-19990x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,//2000-20090x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,//2010-20190x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,//2020-20290x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,//2030-20390x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,//2040-20490x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50, 0x06b20,0x1a6c4,0x0aae0,//2050-20590x0a2e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,//2060-20690x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,//2070-20790x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,//2080-20890x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252,//2090-20990x0d520////2100
因為能找到最早的農歷十六進制數據是1900年。且通過查詢,可以得知,陽歷1900.1.31,正好是農歷1900.1.1。這樣便于推算
在上面,我們可以由農歷十六進制數據推算,每個月有多少天,以及是否閏月,那么久很容易推算出某一年有多少天,但是,我們是將其轉為二進制來計算的。如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=0x8000; $i>=0x10; $i>>=1){ $curNum=0x04bd8 && $i ? 1 :0;}
這樣計算農歷y年有多少天程序就比較容易實現
/** *計算農歷y年有多少天 **/ public function getYearDays($y){ $sum = 348;//12*29=348,不考慮小月的情況下 for($i=0x8000; $i>=0x10; $i>>=1){ $sum += ($this->dataInfo[$y-1900] & $i)? 1: 0; } return($sum+$this->leapDays($y)); } /** *獲取某一年閏月的天數 **/ public function leapDays($y){ if($this->leapMonth($y)){ return(($this->dataInfo[$y-1900] & 0x10000)? 30: 29); } else { return(0); } } /** *計算哪一月為閏月 */ public function leapMonth($y){ return ($this->dataInfo[$y-1900] & 0xf); }
當我們在求一個時間的時間戳的時候,如果時間太靠前或者是太靠后,PHP是不能給出正確的解析,如下
var_dump(strtotime('1900-1-31')); //返回falsevar_dump(strtotime('2050-3-01'));//返回false
查看手冊可以看到
對于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')); //-2206425600var_dump($c2->format('U'));//4449484800PHP編程
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答