本文写于2015-08-06 11:05。
由于技能进步,个中的描述不一定适用于现在,请自行定夺。

PHP从5起,新增了关于日出和日落的函数:date_sunrise、date_sunset(PHP5.1.2起还有date_sun_info函数,有兴趣的可以看看),这对付要根据日出日落韶光改变网页内容的人来说是一个福音。
我由此想到了可以先用JS获取当前所在位置,然后用PHP打算当前位置日出日落韶光的办法,但是写动身序来并不随意马虎。

这个问题我曾经在 JS代码实现白天黑夜引入不同的CSS - Ben's Lab 的评论中提到过:

phpgps坐标归档获取当前地点地位并盘算当前地位日出日落时光JSPHP Bootstrap

评论

如今,我做出来了,我觉得做这个的过程不是“so difficult”,而是“so so so so so so so so so so so so so so so difficult”!

首先,看一下粗略的流程图吧:

流程图

为什么要用百度舆图呢?目前的浏览器都支持定位功能,能够得到准确度比较高的经纬度。
但由于已知缘故原由,某些浏览器(如Chrome)无法利用HTML5内置的定位功能。

百度舆图的干系API可以到官网查看,新版的API须要得到AppKey才能利用。

我有时创造,百度舆图所供应的坐标是经由转换的!

国际经纬度坐标标准为WGS-84,海内必须至少利用国测局制订的GCJ-02,对地理位置进行首次加密。
百度坐标在此根本上,进行了BD-09二次加密方法,更加保护了个人隐私。
百度对外接口的坐标系并不是GPS采集的真实经纬度,须要通过坐标转换接口进行转换。

我所须要的坐标当然是真实的经纬度坐标了!
因此,我就查找将百度坐标转换为原始坐标的方法,却创造百度不供应这种方法。
我又到网上找,创造目前没有精确的转换方法,你懂的。
同时,我还理解了各种坐标。
感兴趣的人可以看一下 关于百度舆图坐标转换接口的研究 - Rover.Tang - 博客园 和 [转]地球坐标 火星坐标 百度坐标 相互转换 。

我在zdoz.net上找到了一个很不错的API,转换结果可以精确到小数点后5位。
但我后来在测试时创造,由于涉及到跨域获取,无法利用。
幸好我又找到了一个很好的JS(原文也供应PHP版的)能够办理坐标转换的问题:GPS坐标互转:WGS-84(GPS)、GCJ-02(Google舆图)、BD-09(百度舆图),我试了一下,效果很不错,可以精确到小数点后4位(PS:据我测试,日出日落韶光打算中,经纬度须要精确到小数点后1位就行了)。
源码并没直接供应百度坐标到GPS坐标的转换函数,须要间接弄。

function bd2GPS(lng,lat){ var arr2 = GPS.bd_decrypt(lat,lng); var arr3 = GPS.gcj_decrypt(arr2['lat'], arr2['lon']); return {'lng': arr3['lon'], 'lat': arr3['lat']};}

转换为坐标之后,须要将坐标值发送到做事器端进行打算再传回,须要AJAX。
于是我立时在 W3School 补习了AJAX。
我利用的是GET办法,这样比较快。

我让PHP输出JSON语句,然后在客户端上解析并输出。
这时我才知道,传回的JSON语句须要用eval()函数才能解析成功!

PHP的编写是最难的,倒不是由于代码,而是由于你要考虑很多事情。

首先,我们要考虑时区问题。
虽然我用的做事器时区为东八区(UTC+8,北京韶光所对应的时区),但我想做一个可移植式的API,这样,无论你的做事器在哪里,你都能在本地收到当前时区对应的韶光。

怎么做呢?这时须要客户端发送客户端时区信息。

var d = new Date();var localOffset = -d.getTimezoneOffset()/60;

为什么要加负号呢?由于getTimezoneOffset()返回的是UTC-本地(我习惯用UTC而不是GMT)。
如果不加负号,在北京韶光状态下localOffset的值是-8。
这个 W3School 并没有说。

然后在PHP中获取做事器时区信息,并打算时差。
这须要写一个函数,打算做事器时区与UTC的时差。
这函数是在php.net上看到的,链接在代码的第二行:

<?php/ http://php.net/manual/zh/function.timezone-offset-get.php Returns the offset from the origin timezone to the remote timezone, in seconds. @param $remote_tz; @param $origin_tz; If null the servers current timezone is used as the origin. @return int;/function get_timezone_offset($remote_tz, $origin_tz = null) { if($origin_tz === null) { if(!is_string($origin_tz = date_default_timezone_get())) { return false; // A UTC timestamp was returned -- bail out! } } $origin_dtz = new DateTimeZone($origin_tz); $remote_dtz = new DateTimeZone($remote_tz); $origin_dt = new DateTime(\公众now\"大众, $origin_dtz); $remote_dt = new DateTime(\"大众now\"大众, $remote_dtz); $offset = $origin_dtz-&gt;getOffset($origin_dt) - $remote_dtz->getOffset($remote_dt); return $offset;}//Examples:// This will return 10800 (3 hours) ...//$offset = get_timezone_offset('America/Los_Angeles','America/New_York');// or, if your server time is already set to 'America/New_York'...//$offset = get_timezone_offset('America/Los_Angeles');// You can then take $offset and adjust your timestamp.//$offset_time = time() + $offset;?>

利用时,代码如下:

$severOffset = get_timezone_offset('UTC')/3600;

获取客户真个韶光戳($localOffset是客户端时区):

$offsetDifference=$severOffset-$localOffset;$localTimeStamp=time()-$offsetDifference3600; //这是客户真个韶光戳

然后就可以用到日出日落韶光打算了($lat、$lng分别为纬度、经度):

$sunRiseStamp=date_sunrise($localTimeStamp,SUNFUNCS_RET_TIMESTAMP,$lat,$lng,90+50/60,$localOffset);$sunSetStamp=date_sunset($localTimeStamp,SUNFUNCS_RET_TIMESTAMP,$lat,$lng,90+50/60,$localOffset);$sunRise=date_sunrise($localTimeStamp,SUNFUNCS_RET_STRING,$lat,$lng,90+50/60,$localOffset);$sunSet=date_sunset($localTimeStamp,SUNFUNCS_RET_STRING,$lat,$lng,90+50/60,$localOffset);

其次,我们要考虑日出日落韶光次序。
有些地方,在一天之内,日出韶光可能会晚于日落韶光。
当然,你很难找到有这样一个地方,我也不知道有没有,但这很主要,以防万一。
代码很大略:

if($sunSetStamp<$sunRiseStamp){//此处写黑夜在一整天之内的代码}else{//此处写白天在一整天之内的代码}

然后,我们要考虑一天的韶光段的划分。
我写的PHP在返回日出日落韶光同时也会返回当前的韶光段。
白天和黑夜是很好划分的,但再细分就出问题了:中式的韶光段划分办法和西式的不一样(中式:上午,中午,下午,晚上,凌晨;西式:morning,noon,afternoon,evening,night,个中晚上和凌晨与evening和night并不一一对应),而且,有些地方的白天或黑夜很短,而如果按小时划分,会涌现很多问题。
于是,我按照春分日和秋分日时各韶光段的位置和比例进行比例划分:

在白天(day),从日出开始白天的5/12~7/12为中午(noon),此韶光段之前为上午(morning),之后为下午(afternoon);

在黑夜(night),前1/4为evening,后3/4为night;黑夜的前半部分为晚上,后半部分为凌晨。

白天、中午霸占两端点值。
evening、晚上霸占结束端点值。

当时的草稿:

草稿1

草稿2

代码:

if($sunSetStamp<$sunRiseStamp){ //黑夜在一整天之内 $divideDay=($sunSetStamp+86400-$sunRiseStamp)/12; $divideNight=($sunRiseStamp-$sunSetStamp)/4; if(($localTimeStamp()>=$sunRiseStamp) || ($localTimeStamp<=$sunSetStamp)) $period=\公众day\公众; else $period=\"大众night\公众; if(($localTimeStamp>=$sunRiseStamp && $localTimeStamp<$sunRiseStamp+5$divideDay) || $localTimeStamp<$sunSetStamp-7$divideDay){ $period_exact_chinese=\"大众上午\公众; $period_exact_western=\公众morning\"大众; } elseif(($localTimeStamp>=$sunRiseStamp+5$divideDay && $localTimeStamp<=$sunRiseStamp+7$divideDay) || ($localTimeStamp>=$sunSetStamp-7$divideDay && $localTimeStamp<=$sunSetStamp-5$divideDay)){ $period_exact_chinese=\"大众中午\"大众; $period_exact_western=\公众noon\"大众; } elseif(($localTimeStamp>$sunSetStamp-5$divideDay && $localTimeStamp<=$sunSetStamp) || $localTimeStamp>$sunRiseStamp+7$divideDay){ $period_exact_chinese=\公众下午\"大众; $period_exact_western=\"大众afternoon\"大众; } elseif($localTimeStamp>$sunSetStamp && $localTimeStamp<=$sunSetStamp+2$divideNight) $period_exact_chinese=\"大众晚上\"大众; elseif($localTimeStamp>$sunSetStamp+2$divideNight && $localTimeStamp<$sunRiseStamp) $period_exact_chinese=\公众凌晨\"大众; if($localTimeStamp>$sunSetStamp && $localTimeStamp<=$sunSetStamp+$divideNight) $period_exact_western=\公众evening\"大众; elseif($localTimeStamp>$sunSetStamp+$divideNight && $localTimeStamp<$sunRiseStamp) $period_exact_western=\公众night\"大众;}else{ //白天在一整天之内 $divideDay=($sunSetStamp-$sunRiseStamp)/12; $divideNight=($sunRiseStamp+86400-$sunSetStamp)/4; if(($localTimeStamp<$sunRiseStamp) || ($localTimeStamp>$sunSetStamp)) $period=\公众night\"大众; else $period=\"大众day\公众; if($localTimeStamp>=$sunRiseStamp && $localTimeStamp<$sunRiseStamp+5$divideDay){ $period_exact_chinese=\"大众上午\"大众; $period_exact_western=\公众morning\"大众; } elseif($localTimeStamp>=$sunRiseStamp+5$divideDay && $localTimeStamp<=$sunRiseStamp+7$divideDay){ $period_exact_chinese=\公众中午\"大众; $period_exact_western=\公众noon\公众; } elseif($localTimeStamp>$sunRiseStamp+7$divideDay && $localTimeStamp<=$sunSetStamp){ $period_exact_chinese=\"大众下午\"大众; $period_exact_western=\"大众afternoon\公众; } elseif(($localTimeStamp>$sunSetStamp && $localTimeStamp<=$sunSetStamp+2$divideNight) || $localTimeStamp<=$sunRiseStamp-2$divideNight) $period_exact_chinese=\"大众晚上\公众; elseif(($localTimeStamp>$sunRiseStamp-2$divideNight && $localTimeStamp<$sunRiseStamp) || $localTimeStamp>$sunSetStamp+2$divideNight) $period_exact_chinese=\公众凌晨\公众; if(($localTimeStamp>$sunSetStamp && $localTimeStamp<=$sunSetStamp+$divideNight) || $localTimeStamp<=$sunRiseStamp-3$divideNight) $period_exact_western=\公众evening\"大众; elseif(($localTimeStamp>$sunRiseStamp-3$divideNight && $localTimeStamp<$sunRiseStamp) || $localTimeStamp>$sunSetStamp+$divideNight) $period_exact_western=\公众night\公众;}

然后,我们要考虑是否有极昼极夜。
如果有极昼极夜,date_sunrise和date_sunset返回值为空。
以是我们还要验证其返回值是非为空:

if($sunRise!=\"大众\"大众 and $sunSet!=\"大众\公众) { //此处写非极昼极夜代码}else { //此处写极昼极夜代码}

那怎么更详细地划分极昼极夜呢?我们可以根据纬度和日期进行划分:春分日(3月21日,以北半球为准)和秋分日(9月23日)无极昼极夜;春分日到秋分日之间,北半球有极昼,南半球有极夜;秋分日到春分日,恰好相反。
而且极昼极夜期间,韶光段只有一个。

我们还要考虑到春分日和秋分日时,南北极点的情形。
北极点春分日相称于日出,秋分日相称于日落;南极点恰好相反。
按上面的规定,均视为白天。

代码如下(放在上面的代码的//此处写极昼极夜代码处):

$dateNum=idate(\公众z\"大众,$localTimeStamp);if(idate(\"大众L\"大众,$localTimeStamp)==0){ //平年if($dateNum>=51 && $dateNum<234){ //春分日到秋分日if($lat>0){ //北纬 $period=\"大众day\公众; $period_exact_chinese=\公众白天\"大众; $period_exact_western=\公众day\"大众; } else{//南纬 $period=\"大众night\"大众; $period_exact_chinese=\"大众黑夜\"大众; $period_exact_western=\公众night\公众; } } else{//秋分日到春分日if($lat>0){//北纬 $period=\公众night\"大众; $period_exact_chinese=\"大众黑夜\"大众; $period_exact_western=\公众night\公众; } else{//南纬 $period=\"大众day\"大众; $period_exact_chinese=\"大众白天\公众; $period_exact_western=\"大众day\"大众; } }}else{ //闰年if($dateNum>=52 && $dateNum<235){ //春分日到秋分日if($lat>0){ //北纬 $period=\公众day\公众; $period_exact_chinese=\"大众白天\公众; $period_exact_western=\"大众day\公众; } else{ //南纬 $period=\公众night\公众; $period_exact_chinese=\"大众黑夜\"大众; $period_exact_western=\"大众night\"大众; } } else{ //秋分日到春分日if($lat>0){ //北纬 $period=\"大众night\"大众; $period_exact_chinese=\"大众黑夜\公众; $period_exact_western=\"大众night\"大众; } else{ //南纬 $period=\公众day\"大众; $period_exact_chinese=\公众白天\公众; $period_exact_western=\公众day\"大众; } }}

末了,千万别忘了在代码最前面加上header('Content-type: application/json; charset=utf-8');

输出语句:

//非极昼极夜echo '{\"大众sunrise\公众:\"大众'.$sunRise.'\"大众,\公众sunset\"大众:\公众'.$sunSet.'\"大众,\"大众period\"大众:\"大众'.$period.'\"大众,\"大众period_exact_chinese\"大众:\"大众'. $period_exact_chinese.'\"大众,\"大众period_exact_western\"大众:\"大众'.$period_exact_western.'\"大众}';//极昼极夜echo '{\"大众sunrise\"大众:\"大众null\公众,\"大众sunset\"大众\公众null\"大众,\公众period\公众'.$period.'\公众,\"大众period_exact_chinese\公众:\"大众'. $period_exact_chinese.'\公众,\"大众period_exact_western\公众:\"大众'.$period_exact_western.'\"大众}';

这个过程是一个极其烧脑的过程:看花括号的时候,总是看错;打算各韶光段的范围时,想了半天才想通;构思代码足足花了我三天……不过,总算是大功告成了!

我已把这些东西上传到GitHub,项目命名为SunGet,欢迎Fork或Star。
个中,master分支存放的是sunget.php和演示文档,GeoSunTime分支存放的是定位后打算日出日落韶光的文档。

我在写自述文档时,还自己翻译成英文版放在中文版下面,好累啊……