由于php 32位使用 int 類型保存時間戳,也就是從1970 00:00:00 到當前時間的秒數。
而32位int 數字的取值范圍是 -2147483648 到 2147483647。
所以當 時間戳為最大值 2147483647 時,表示的時間是 2038-01-19 03:14:07 或北京時間 2038-01-19 11:14:07 (為了表述方便,下文中,將這個臨界點時間稱之為 T0)。
而當時間大于這個時間時,php很多內置函數都會出錯。
比如
當日期和時間大于 北京時間 2038-01-19 11:14:07 時
time()函數,原本應該返回時間戳,現在會始終返回-1。
date("Y-m-d H:i:s")函數,會返回 1970-01-01 07:59:59(北京時間),其實也是因為 time()=-1導致的,date默認的第二個參數就是time()。
同樣,mktime() 等函數也會異常。
上網查了解決辦法,
1、換用64位系統。這里說的64位系統,需要操作系統、web服務系統,以及PHP都要64位的。
2、使用php5.2之后推出的 DateTime 類。
首先說第一種方法,因為我的服務器建設在Windows系統上,然后又有幾個自制插件,這些插件在php 64位下面可能不能使用,因此這個方法不能用。
再說DateTime類,網上幾乎幾十篇文章都說使用DateTime類就能解決2038年問題。
我在自己的服務器上測試了一下,使用DateTime類似乎確實可以讓日期超過2038上限,各種轉換,都沒問題,這里我不具體說明,大家網上搜"php datetime",都有說明。
但是,我在把服務器的時間設置為2040年4月18日的時候,發現,datetime 類依然無法獲取當前時間。代碼如下:
$date = new DateTime();
echo $date->format('Y-m-d H:i:s');
輸出的還是 1970-01-01 07:59:59
但是,如果使用 $date->setDate(2040,4,18) 之后,再顯示,再輸出時間戳等,都是正常的代碼如下:
$date = new DateTime();
$date->setDate(2040,4,18);
$date->setTime(10,24,11);
echo $date->format('Y-m-d H:i:s')."
\r\n";
echo $date->format('U')."
\r\n";
這時 輸出時間 2040-04-18 10:24:11 ,以及時間戳 2218328651 都是正常的。
問題在于,datetime 類可以解決 2038年之后的時間的各種運算和轉換,但是當系統日期在2038年那個T0時間之后,php系統根本無法獲取當前時間。
我還試了 new DateTime("today");new DateTime('+2 days');new DateTime('tomorrow'); 等等,都無法獲取今天,明天,后天等日期。
這時,整個php 系統無法獲取當前的年月日和時間。
然后我開始在php的系統數組 $_SERVER 中尋找,看看哪里能找到和時間相關的內容,終于被我找到一個 $_SERVER["REQUEST_TIME"],這個實際上是一個記錄用戶刷新頁面時php相應時刻的時間。它的值,在T0之前,和time()是一致的,但是,當T0之后,它就變成負數了。那么,怎么通過 $_SERVER["REQUEST_TIME"] 來獲取真實的 時間戳呢?
很簡單,32位int 數字的取值范圍是 -2147483648 到 2147483647,轉成2進制就會發現,其實是最高位用作符號位,最高位0表示正數,最高位1表示負數,當數字達到 2147483647后,二進制 就是 01111111 11111111 11111111 11111111(31個1),這時就是T0時刻的時間戳,繼續+1 以后,變成了 10000000000000000000000000000000 (31個0),如果是無符號32位整數,就是 2147483648(正數) 但是在有符號的整數里,最高位1表示負數,就是 -2147483648(負數),而 $_SERVER["REQUEST_TIME"] 的特性是根據時間的推移進行累加。所以,它的時間線如下:
T0 之前:它等于 1970 00:00:00 到當前時間的秒數,和time()相同
T0 時: 它等于 2147483647
T0 后1秒: 它等于 2147483647+1=2147483648 被表示為 -2147483648 我們把 -2147483648 記作 T1,T1=T0+1秒的時刻
T0 后N秒:-2147483648-1+N
所以,當 $_SERVER["REQUEST_TIME"]<0 時,真正的時間戳為 $_SERVER["REQUEST_TIME"]-(-2147483648)+ 2147483647。
其中 $_SERVER["REQUEST_TIME"]-(-2147483648)表示 T1(變成負數,即T0+1秒) 時刻到當前時間 過了多少秒。
據此,寫出一個新的取代time()的函數,該函數在系統時間超過T0 時,也能返回正確的時間戳,但是它的范圍是無符號32位上限 4294967295,北京時間 2106-02-07 06:28:15。在這個時間之前,應該都可以正常使用。
function sunTime(){
if($_SERVER["REQUEST_TIME"]>0){
$t=$_SERVER["REQUEST_TIME"];
}else{
$t0=PHP_INT_MAX; // 第 2147483647 秒 再過一秒為 2147483648秒,但最高位變成1,系統中為 -2147483648
$t1=0-$t0-1; // t0后面1秒,瞬間變成負數,值為 -2147483648
$t2=($_SERVER["REQUEST_TIME"]);//雖然$_SERVER["REQUEST_TIME"]變成了負數,但是 系統依然通過 +1秒 來計時
$t=$t2-$t1+$t0.""; //t2-t1 就是變成負數后過了多少秒,t0就是變成負數前的秒數。
}
$date=new datetime("@".$t);
$timemark=$date->format("U");
return $timemark;
}
所以,目前網上很多人都以為使用 DateTime類可以解決問題,殊不知等時間真正到了2038那個時間之后,php系統獲得當前時間都會出錯。而我這方法也是目前網上唯一存在的方法。
陽光浪子
2018-04-18