wordpress中的時區問題

歡迎轉載!轉載時請註明出處:http://blog.csdn.net/nfer_zhuang/article/details/51285483

引言

我在一個項目中需要設置截至時間,但是發現過了截至時間後仍然有效,即如下的代碼判斷沒有表現正常:

if ( strtotime("2016-04-15 23:00:00") > time() ) {
    // available, do something
}
else {
    // unavailable, stop do something
}

即,當過了設定的2016-04-15 23:00:00之後,仍然是可以操作的,因此當時爲了暴力解決,直接在過了23點之後,加了一個false的邏輯與運算:

if ( strtotime("2016-04-15 23:00:00") > time() && false) {
    // available, do something
}

當然,在我寫這篇總結的時候,就算去掉false的邏輯與運算,程序的運行也是符合預期的。現在就是分析和總結一下wordpress的時區問題。

php環境和shell環境的測試

測試代碼如下:

<?php
echo "before set timezone PRC, get timezone:".date_default_timezone_get()."\n";
test();
date_default_timezone_set('PRC');
echo "\nafter set timezone PRC, get timezone:".date_default_timezone_get()."\n";
test();

function test() {
    echo " ".strtotime('2016-04-15 23:00:00')." strtotime('2016-04-15 23:00:00'):\n";
    echo " ".time()." time():\n";
    echo " ".exec('date +%s')." exec('date +%s'):\n";
}

運行結果如下:

before set timezone PRC, get timezone:Asia/Shanghai
 1460732400 strtotime('2016-04-15 23:00:00')
 1460807831 time()
 1460807831 exec('date +%s')

after set timezone PRC, get timezone:PRC
 1460732400 strtotime('2016-04-15 23:00:00')
 1460807831 time()
 1460807831 exec('date +%s')

而shell下的date命令運行結果如下:

# date 
Sat Apr 16 20:14:03 CST 2016

首先解釋幾個專有名詞:

  • CST:百度百科顯示CST可以表示四個時區的縮寫:
    • 美國中部時間:Central Standard Time (USA) UT-6:00
    • 澳大利亞中部時間:Central Standard Time (Australia) UT+9:30
    • 中國標準時間:China Standard Time UT+8:00
    • 古巴標準時間:Cuba Standard Time UT-4:00

而在這裏,我們可以使用藉助date命令的另外一個參數來明確一下具體是哪個時區:

# date +%:z
+08:00

這樣,我們確認了時區設置是對的,是中國的+08:00時區。

  • Asia/Shanghai:根據wikipedia的頁面,這個屬於國際上通用的北京時間
  • PRC:People’s Republic of China的縮寫,而php的官方說明中是不建議使用PRC作爲時區參數的,具體見頁面

因此,我們上面的代碼實際上是需要更正的:

<?php
echo "before set timezone Asia/Shanghai, get timezone:".date_default_timezone_get()."\n";
test();
date_default_timezone_set('Asia/Shanghai');
echo "\nafter set timezone Asia/Shanghai, get timezone:".date_default_timezone_get()."\n";
test();

function test() {
    echo " ".strtotime('2016-04-15 23:00:00')." strtotime('2016-04-15 23:00:00'):\n";
    echo " ".time()." time():\n";
    echo " ".exec('date +%s')." exec('date +%s'):\n";
}

而,設置時區前後的值都是”Asia/Shanghai”,因此獲取的時間戳也是正常的。

wordpress環境測試

測試代碼如下:

<?php
require("/home/nfer/git/WordPress/WordPress/wp-load.php");

echo "before set timezone Asia/Shanghai, get timezone:".date_default_timezone_get()."\n";
test();
date_default_timezone_set('Asia/Shanghai');
echo "\nafter set timezone Asia/Shanghai, get timezone:".date_default_timezone_get()."\n";
test();

function test() {
    echo " ".strtotime('2016-04-15 23:00:00')." strtotime('2016-04-15 23:00:00'):\n";
    echo " ".time()." time():\n";
    echo " ".exec('date +%s')." exec('date +%s'):\n";
}

運行結果如下:

before set timezone Asia/Shanghai, get timezone:UTC
 1460761200 strtotime('2016-04-15 23:00:00'):
 1460811693 time():
 1460811693 exec('date +%s')

after set timezone Asia/Shanghai, get timezone:Asia/Shanghai
 1460732400 strtotime('2016-04-15 23:00:00'):
 1460811693 time():
 1460811693 exec('date +%s'):

從運行結果上可以看到的是,在引入了wordpress相關文件後,獲取的時區值竟然是UTC,所以對應的通過strtotime()解析出的時間戳也和北京時間差8個小時。

wordpress時區設置

回到wordpress的設置頁面,我們發現實際上是已經設置了+8:00時區的,具體見圖:
這裏寫圖片描述
那麼,依然已經設置了正確的時區,爲什麼還會出現時區異常呢?

wordpress文件引入

查看上面的測試代碼,我們引入的是wp-load.php文件,打開這個文件,實際上是做了以下兩個事情:

/** Define ABSPATH as this file's directory */
define( 'ABSPATH', dirname("/home/nfer/git/WordPress/WordPress/wp-load.php") . '/' );

/** The config file resides in ABSPATH */
require_once( ABSPATH . 'wp-config.php' );

因此,我們需要進一步查看wp-config.php文件,除了和數據庫設置相關的,關鍵的是又引入了wp-settings.php文件。當我們打開wp-settings.php文件時,關鍵問題出現了,在第42行有一下代碼:

// WordPress calculates offsets from UTC.
date_default_timezone_set( 'UTC' );

經過,測試,去除這一行代碼,測試程序運行正常,符合預期,看來問題就出在這裏了。

官方說明

https://wordpress.org/support/topic/why-does-wordpress-set-timezone-to-utc

This is mostly a holdover from when WordPress supported PHP 4.

PHP 4 didn’t have a way to set timezones like that, and so do timezone related calculations. In order to work around this, WordPress implemented its own time calculation code.

Originally, the timezone setting was limited to selecting +1 hours, +2 hours, etc. It was annoying and you had to change it twice a year for daylight savings time.

So 4 years ago, around WordPress 2.8 or so, I wrote the patch that added the new method of choosing timezone. If PHP 5 was on the server, it would get the timezone information from PHP and then apply the calculations automatically to handle daylight savings. This is where the timezone_string came from.

However, because PHP 4 was still being supported, the old code for calculating timezone was retained, and to make all WordPress instances behave identically, the default timezone was set to UTC. My first patch actually did set the date_default correctly, but this caused strange and inconsistent behavior because much of the core assumed UTC for date() calls and the like.

This may be fixed in the future, but it’s a big job. Lots of code to deal with and adjust to the new way of doing things. It’s not a priority though, because it works for now. However, it is a bit of a minor performance hit (see http://core.trac.wordpress.org/ticket/23132), so that might affect things.

Note that you should not call date_default_timezone_set to change the timezone, because much of the core still assumes UTC when doing calculations. This can affect things like scheduling of posts and cause other strange behavior.

Instead, you should use the functions in WordPress to retrieve times, such as current_time, for example. Alternatively, if you do change the date default, make sure to change it back to what it was before after you’re done.

看來,官方說明中還不建議調用date_default_timezone_set函數來修改時區,如果要獲取時間,可以調用wordpress內建函數,比如current_time()

但是,我們這裏是需要解析一個時間字符串,這種情況下,wordpress沒有提供響應的內建函數,怎麼辦呢?

修正的方法

同樣是查看wordpress內建函數的實現,比如mysql2date()的源碼如下:

function mysql2date( $format, $date, $translate = true ) {
    if ( empty( $date ) )
        return false;

    if ( 'G' == $format )
        return strtotime( $date . ' +0000' );

    $i = strtotime( $date );

    if ( 'U' == $format )
        return $i;

    if ( $translate )
        return date_i18n( $format, $i );
    else
        return date( $format, $i );
}

注意,這裏如果if ( 'G' == $format )條件滿足的話,它主動對$date添加了時區部分,那麼是不是我們也需要指定具體的時區呢?
修改後的代碼如下:

<?php
require("/home/nfer/git/WordPress/WordPress/wp-load.php");

echo "before set timezone Asia/Shanghai, get timezone:".date_default_timezone_get()."\n";
test();
date_default_timezone_set('Asia/Shanghai');
echo "\nafter set timezone Asia/Shanghai, get timezone:".date_default_timezone_get()."\n";
test();

function test() {
    echo " ".strtotime('2016-04-15 23:00:00 +0800')." strtotime('2016-04-15 23:00:00 +0800')\n";
    echo " ".time()." time()\n";
    echo " ".exec('date +%s')." exec('date +%s')\n";
}

注意,在日期字符串中,我們主動加入了北京時區" +0800",那麼這次的運行結果呢?

before set timezone Asia/Shanghai, get timezone:UTC
 1460732400 strtotime('2016-04-15 23:00:00 +0800')
 1460821607 time()
 1460821607 exec('date +%s')

after set timezone Asia/Shanghai, get timezone:Asia/Shanghai
 1460732400 strtotime('2016-04-15 23:00:00 +0800')
 1460821607 time()
 1460821607 exec('date +%s')

請注意,這次兩次通過strtotime()解析的字符串都是一樣的了。Bingo!

總結

這次的問題實際上是WordPress兼容老版本的問題,屬於WordPress開發上的一個坑。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章