2018年9月6日 星期四

PHP strtotime 月份加減

PHP 的 strtotime 可以進行月份的加減。
但 PHP 進行時,會保留原來的「日」,再判斷是否需進行時間的偏移,所以可能導致最終的日期跟預想的不一樣。
例如:
$cur_date = "2016-03-31";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($cur_date))));//2016-03-02
var_dump(date("Y-m-d", strtotime("+1 month", strtotime($cur_date))));//2016-05-01
2016-03-31 減一個月變成 2016-02-31,但2016年2月只到29號,所以 2016-02-31 => 2016-03-02
2016-03-31 加一個月變成 2016-04-31,但4月只到30號,所以 2016-04-31 => 2016-05-01

這結果,可能跟預想的不太一樣。
但如果只是要取月份加減後的月初、月底,或只著重在結果的月分準確即可,
那可使用「first day of」(取月初)、「last day of」(取月底)來處理。
加減過程中,沒了原本的「日」,也就沒最後因為「日」而導致月份的偏移了。
$cur_date = "2016-03-31";
var_dump(date("Y-m-d", strtotime("first day of -1 month", strtotime($cur_date))));//2016-02-01
var_dump(date("Y-m-d", strtotime("last day of -1 month", strtotime($cur_date))));//2016-02-29
var_dump(date("Y-m-d", strtotime("first day of +1 month", strtotime($cur_date))));//2016-04-01
var_dump(date("Y-m-d", strtotime("last day of +1 month", strtotime($cur_date))));//2016-04-30


最後測試一下跨年是否有影響。
結果:跨年度的話,似乎「年」能先準確的計算出來。至於最後會不會自動偏移,一樣是看該「年-月」是否有該日期決定。
$cur_date = "2017-03-29";
var_dump(date("Y-m-d", strtotime("-13 month", strtotime($cur_date))));//2017-02-29

$cur_date = "2017-03-30";
var_dump(date("Y-m-d", strtotime("-13 month", strtotime($cur_date))));//2016-03-01
var_dump(date("Y-m-d", strtotime("first day of -13 month", strtotime($cur_date))));//2016-02-01
var_dump(date("Y-m-d", strtotime("last day of -13 month", strtotime($cur_date))));//2016-02-29


參考:
令人困惑的strtotime | 风雪之隅


2018年8月19日 星期日

JavaScript RegExp 的 lastIndex 屬性

JavaScript 正規表示式 RegExp 物件,當有使用「g」global match 參數時,
會有一個 lastIndex  屬性,記錄上一次批配到的字串,後面的位置(預設值為0)。

RegExp.lastIndex  屬性:
  1. RegExp.exec()、RegExp.test(),都是以 lastIndex 當作匹配比對起點
  2. 當匹配比對失敗,lastIndex 會重設為 0
  3. RegExp.lastIndex 值可手動設定

範例:
var str = 'abcde';
var RegExpObj = new RegExp('b', 'g');
RegExpObj.lastIndex; //0  預設值為 0

//第一次比對,比對成功,lastIndex 變為 2
RegExpObj.test(str); // true
RegExpObj.lastIndex; //2

//第二次比對,從字串位置 2 開始比對,比對失敗,lastIndex 自動重置為 0
RegExpObj.test(str); // false
RegExpObj.lastIndex; //0

//第三次比對,又從字串位置 0 開始比對,比對成功,lastIndex 變為 2
RegExpObj.test(str); // true
RegExpObj.lastIndex; //2

//手動將 lastIndex 設為 1,再次比對,可成功比對到字串位置 1 的 「b」
RegExpObj.lastIndex=1; 
RegExpObj.lastIndex; //1
RegExpObj.test(str); // true


參考:
JavaScript lastIndex 属性
regexp.lastIndex - JavaScript | MDN
regex - Bug with RegExp in JavaScript when do global search - Stack Overflow

PHP 判斷是否為搜尋引擎爬蟲訪問網頁

要判斷是否為搜尋引擎爬蟲訪問網頁,可以從 $_SERVER["HTTP_USER_AGENT"] 字串判斷。
一般有以下兩種判斷方法。
方法1:找出「搜尋引擎爬蟲」可能的 User-Agent,符合的即為「搜尋引擎爬蟲」。
方法2:找出「瀏覽器」可能的 User-Agent,不符合的即是「網路爬蟲」,這邊當做都是「搜尋引擎爬蟲」。當然,如果是要較精準的分辨出「搜尋引擎爬蟲」和其他網路爬蟲,還是要使用方法1。

一般情況,大都只是要分辨出所有網路爬蟲(搜尋引擎、其他爬蟲),所以這邊選用方法2,因為常用的瀏覽器 User-Agent,比較容易知道、也可自己測試取得,
而且一般使用者使用瀏覽器時,應該都會傳送正常的 User-Agent。
所以應該可以放心將不符合的當作是網路爬蟲(搜尋引擎爬蟲)。
檢查方法:
/**
 * 判斷是否為網路爬蟲
 * @return boolean
 */
function webCrawlerDetect() {
    $pattern = '/firefox|chrome|msie|opera|safari|edge|yabrowser|maxthon|konqueror|netscape|lynx|links/i';//瀏覽器
    return !(isset($_SERVER['HTTP_USER_AGENT']) && preg_match($pattern, $_SERVER['HTTP_USER_AGENT']));
}
其他:
可能會在瀏覽器的 User-Agent 發現其他瀏覽器的關鍵字,
例如 Chrome、Edge 的 User-Agent 可能發現 Mozilla、Safari。
這是因為有的網站會針對不同瀏覽器特有功能,做適當的回應。
所以當新的瀏覽器,也相容其他瀏覽器特有功能時,瀏覽器便用此方式,讓網站也能如期顯示。
參考 WebAIM: History of the browser user-agent string

2018年7月28日 星期六

Netbeans 設定預設使用 UTF8 編碼開啟檔案

Netbeans 專案可以設定使用的文字編碼,例如設定為 UTF8,
但即使在 UTF8 編碼的專案中,嘗試「開啟」或「Diff to...」非專案資料夾內的檔案,
能會使用原本預設的編碼開啟,而不是專案設定的編碼。
所以可能出現「cannot be safely opened with encoding x-windows-950」之類的錯誤訊息。
(嘗試使用預設的 BIG5 編碼開啟不是 BIG5 編碼的檔案,強制打開後會顯示亂碼)

若預開啟的檔案維 UTF8,可以將 Netbeans 預設開啟檔案的編碼改為 UTF-8
  1. 找到 netbeans.conf 設定檔
    一般在「C:\Program Files\NetBeans 8.2\etc\netbeans.conf」
  2. 將設定檔中的 netbeans_default_options 選項,加上(或修改為)「-J-Dfile.encoding=UTF-8」。
    例如:
    netbeans_default_options="-J-client -J-Xss2m -J-Xms32m -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dsun.zip.disableMemoryMapping=true"
    
    在最後加上「-J-Dfile.encoding=UTF-8」,再重開 Netbeans 即可。
    netbeans_default_options="-J-client -J-Xss2m -J-Xms32m -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dsun.zip.disableMemoryMapping=true -J-Dfile.encoding=UTF-8"
    


參考:
How to change default encoding in NetBeans 8.1

2018年7月10日 星期二

PHP SimpleXMLElement 輸出的 XML 增加 CDATA

使用 SimpleXMLElement 產生 XML 時,若遇到內容有特殊字元,
SimpleXMLElement 不會自動轉換特殊字元, 也不會自動加上 CDATA
所以內容有特殊字元時,若沒先處理再傳給 SimpleXMLElement,便會出錯。

若想使用加上 CDATA 的方式,可如以下幫 SimpleXMLElement 加上 addCData 功能。
class SimpleXMLExtended extends \SimpleXMLElement {
    /**
     * 將節點的值加上 CDATA
     * @param string $cdata_text 節點的值
     */
    public function addCData($cdata_text) {
        $node = dom_import_simplexml($this);
        $no = $node->ownerDocument;
        $node->appendChild($no->createCDATASection($cdata_text));
    }
}
使用
$sxe = new \SimpleXMLExtended('<xyz></xyz>');
$sxe->addChild('aa')->addCData('test');
$xml = $sxe->asXML();
//輸出結果
//<?xml version="1.0"?>
//<xyz><aa><![CDATA[test]]></aa></xyz>


參考:
php - How to write CDATA using SimpleXmlElement? - Stack Overflow