结合上次做的日历,提前粘贴一下效果图
当前月份
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyN0YzMzIjMxETMyYDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
下一个月
相关概念
阳历,有很强的规律性。每年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>';
}
效果图如下
对比百度中日历
测试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。
通过查询百度日历,也可以验证上面我推算
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。这样便于推算
怎样计算农历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 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