2019-06-04

JSON 日期碎碎念

最近(終於)在測 gwt-jackson,負責在 server side 噴 JSON 的是 Gson,然後測到日期(java.util.Date)的時候炸了一輪,所以來碎念一下留個紀念… XD

一開始以為是 gwt-jackson 炸掉,畢竟 GSON 資格老關係好(?),GWT 現在還有多少人在用都是個問題 lol。但是狗了一圈沒發現啥災情,事情開始有點詭異…

那找「gson date」試試看… WTF?GSON 預設的日期格式會隨平台不同而變?這就別提什麼 W3C 之類的標準了,根本亂搞一通嘛… =="

不過至少有解,就是可以用 new GsonBuilder().setDateFormat() 來設定 format 然後用這個 builder 來建立 Gson instance。

那麼,什麼是 JSON 標準定義的日期格式呢?根據這個 stackoverflow 的說法,JSON 根本沒做出定義… =="

好吧,至少 JS / W3C 貌似有,就是 ISO-8601

因為懶得查,所以直接狗「java dateformat 8601」,我的搜尋結果第一條是 stackoverflow,不過那是要把字串還原回 Date,不太對題。第二條跟第三條(居然)是簡體中文,都是對岸的 CSDN。

先說第三條,文章裡頭給的 pattern 是 yyyy-MM-dd'T'HH:mm:ss.SSS'Z',hmmm… 看起來跟前面 stackoverflow 附的範例一樣,丟下去跑也能正常 parse,太好了可以收工了… 才怪… 得到時間不對,整整多了 8 小時。WTF?時區問題?

算啦算啦… 去測第二條吧,直接拿文章內說可行的 yyyy-MM-dd'T'HH:mm:ss:SSSZZ 來試試看… 連 parse 都過不了?WTF?等等,為什麼反覆測試下發現有時候會 parse 過、而且值也正確?但有時候卻又 parse 不過?幹這是七月半提前報到嗎?

鬼打牆了 n 分鐘之後,終於發現… 測試會過的時候是測完第三條之後把 Z 前後的單引號拔掉,而測試不過的時候是直接複製第二條的文字…

對,X 他 X 的第二個給的 pattern 根本有問題,按照 ISO-8601 的規格,在 sec 跟 ms 中間應該是「.」而不是「:」。肉眼不仔細比對根本很難發現… Orz

到了這個時候,真的是被嚇到了,乖乖回頭看文件。在 Java API 的定義當中,「X」跟「Z」都表示時區,「X」是 ISO-8601 格式、「Z」是 RFC-822 格式(不過目前看不出差異)。ISO-8601 要求日期與時間之間以一個寫死的大寫「T」連結,寫成 Java 的 pattern 就要前後加上單引號。所以第三條 pattern 尾巴的「Z」是寫死不變的。那,為什麼 parse 會過呢?

ISO-8601 當中規定,如果時區剛好是 UTC(零時區)就顯示 Z,所以得到的是合法的字串,但是除非人在 UTC 不然解出來的實際 Date 就會有時差問題… Orz

心得與結論:

  • 即使有名有號的 library 還是有可能存在奇妙的行為。即使到現在我還是無法替 Gson 在日期上的作法想出任何合理的理由…
    • 寫這篇文章的時候認真找了一下,原來 Gson 有開過 issue-281 還 close 了,整個看起來有點莫名而且最後路人們還是在用 GsonBuilder 這招? WTF?
  • google 出來的結果未必可信,即使排名在很前面。stackoverflow 上也是。像這個解答還有 62 個推,底下也只有一個人指出這是不對的答案。
  • 雖然說官方文件也不是不會出錯,但相比來路不明的 blog,機率還是低的多(雖然啃起來也難過很多)

2019-04-05

GF TextUtil debug 雜記

自從在前前公司接觸 GXT、接著把 Chart 的底層 DrawComponent 給翻了一遍之後,就對這玩意很感興趣,之後陸陸續續以 DrawComponent 為基礎搞了一些東西出來。去年終於搞了一個 GF 版的 TextButton 出來。TextButton重點 惡搞之處在於文字的字體會隨著整體大小而自動調整。好不好用很難說,自己是頗為得意啦… 囧>,因為算是集大成之作:

  • 驗證了 GF Layer 機制的可用性
  • 處理 TextSprite 在視覺上的 y 軸位移問題(雖然沒有相對正統地用後來搞出來的 FontMatrics 來校正 XD)
  • 大幅解決效率問題(因為發現有 Sprite.redraw() 而不用每次都搞 DrawComponent.redraw()

不過實務上陸陸續續有炸出一些問題,在某些狀況下初始的字體並沒有變成正確的大小。但是因為一直都能 workaround 掉,所以沒提煉 SSCCE、自然也沒深究

2015-11-25

Git repo 之間的 sync 操作步驟

這篇文章是 GitHub 作為 repo host,不過下列操作都沒有 depend on GitHub。或著說,就是不希望用 GitHub 的功能,所以才要有這些步驟,不然 pull request 應該可以解決大部分的狀況。

2015-11-17

試論述 Listbox 的 check 與 Image id 的關係

ZK 版本:6.5.3

故事是這樣開始的:畫面上有一個設定 checkmark=true 的 Listbox。每個 item 除了自動生出來的 checkbox 之外就是 Image,src 是用 data URI(所以基本上沒有 loading 時間的問題),也有設定 height 讓 Listbox 不會長得太可怕。也因此,使用者希望點圖片可以看到夠大的圖、但是又不希望出現 scroll bar。

所以當使用者點圖片的時候,就用 Executions.createComponents() 製造出一個 Window,然後設法讓圖片等比例縮小到長或寬撐滿整個 Window。Window 的程式碼長這樣(VM 就不附了):

<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('ScaleImageViewModel')" 
    closable="true" title="Image" width="90%" height="90%">

    <vlayout vflex="1" hflex="1">
        <div id="cave" vflex="1" hflex="1" style="overflow:auto;">
            <image id="image" src="@load(vm.image)" />
        </div>
        <hlayout hflex="1">
            <button label="Fit Window" w:onClick="resetRatio()" />
            <slider id="scale" hflex="1" w:onScroll="changeRatio()"/>
            <button label="Pre." onClick="@command('prev')" disabled="@load(vm.start)"/>
            <button label="Next" onClick="@command('next')" disabled="@load(vm.end)"/>
        </hlayout>
    </vlayout>
    <script defer="true"><![CDATA[
    zk.Widget.$("$cave").onAfterSize = function() {
        this.$super("onAfterSize");
        resize();
    }

    scaleWgt = zk.Widget.$("$scale");
    caveElmt = $("$cave")[0];
    imageElmt = $("$image")[0];
    imageOriginWidth = imageElmt.width;
    imageOriginHeight = imageElmt.height;

    resetRatio();
    ]]></script>
</window>

<script><![CDATA[
    var scaleWgt;
    var caveElmt;
    var imageElmt; 
    var imageOriginWidth;
    var imageOriginHeight;
    var ratio;

    function resetRatio() {
        var wRatio = caveElmt.offsetWidth / imageOriginWidth;
        var hRatio = caveElmt.offsetHeight / imageOriginHeight;
        setRatio(
            Math.min(
                (wRatio > 1) ? 1 : wRatio,  //不能比 100% 還大,所以最多壓成 1
                (hRatio > 1) ? 1 : hRatio
            )
        );
        scaleWgt.setCurpos(ratio * 100);
    }

    function resize() {
        imageElmt.width = imageOriginWidth * ratio;
        imageElmt.height = imageOriginHeight * ratio;
        console.log("[after] " + imageElmt.width + "x" + imageElmt.height);
    }

    function setRatio(value) {
        ratio = Math.min(1, value); //不能比 100% 還大,所以最多壓成 1
        resize();
    }

    //其實應該要可以直接 call setRatio(),只是 ZK 我實在...
    function changeRatio() {
        setRatio(scaleWgt.getCurpos() / 100);
    }
]]></script>

我想盡量讓事情都只停在 client side 就處理完成,是說我也不確定調整圖片大小這件事情在 server side 能不能順利解。總之,程式碼可能不夠精煉,而且有些莫名其妙地方是靠 trial and error 得到的可行解、完全不明所以…… 只能說我對 ZK 完全沒有愛,能在零零落落毫無章法的 ZK 文間當中湊出這些,我都覺得減壽三天了…… Zzz

好的,前情提要終於講完了,要開始進入正題了。用實際資料測試的時候發現,有些圖片就是無法改變大小(slider 也沒反應)。原本以為是圖片的問題,但是拿 data URI 的值直接塞 browser 能得到正確的 size……

在經過一番折騰之後,終於發現規律:

Listbox 有一個(以上)item 勾選,就會無法改變大小

既然確定是 ZK 搞出來的問題,那要懷疑的東西就少了一點。又一輪的 trial and error 後,終於發現:Image 的 id 不能是 “image”,不然 $("$iamge") 不知道會 select 到哪個 element 去。

是的,只要換個 id,一切統統都沒事了(為了保險起見,我試過 fullImagescaleImage,不保證其他值不會有問題 Zzz)。

WTF?這到底什麼鬼東西?為什麼 Listbox 有沒有 item 被勾選會影響到 $("$image") 的結果?那個 node 還是在 Window 裡頭(不是說 Window 有自己的 id space?)?而為什麼 ZUL 設定的 id 值對應到 JS / DOM 當中居然不是 unique 的?以後開發人員要自己讓 id 值 unique 嗎(不然有 bug 連要從哪裡開始懷疑起都不知道?)

雖然平常就有在 blame ZK,但是沒遇到過這麼令人傻眼的,這件事情足足讓我笑了十分鐘… lol

教練我想寫 GWT…… [淚目]


後來要修正其他功能的 bug,所以重寫了一次,關鍵部份還是一樣,只是程式碼變得清爽一點,茲更新如下:

<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('ScaleImageViewModel')" 
    closable="true" title="Image" width="90%" height="90%">

    <vlayout vflex="1" hflex="1">
        <div id="cave" vflex="1" hflex="1" style="overflow:auto;">
            <image id="scaleImage" src="@load(vm.image)">
                <attribute w:name="setSrc">
                function (src) {
                    this.$setSrc(src);
                    resetRatio();
                }
                </attribute>
            </image>
        </div>
        <hlayout hflex="1">
            <button label="Fit Window" w:onClick="resetRatio()" />
            <slider id="scale" hflex="1" w:onScroll="changeRatio()"/>
            <button label="Pre." onClick="@command('prev')" disabled="@load(vm.start)"/>
            <button label="Next" onClick="@command('next')" disabled="@load(vm.end)"/>
        </hlayout>
    </vlayout>
    <script defer="true">
    //第一次 Image.setSrc() 不會真正 resetRatio(),所以得在 defer 的 script 指定來一次
    resetRatio();
    </script>
</window>

<script><![CDATA[
function resetRatio() {
    var imageElmt = $("$scaleImage")[0];

    if (!imageElmt) { return; }

    var tempImage = new Image();
    tempImage.src = imageElmt.src;

    var caveElmt = $("$cave")[0];
    var wRatio = caveElmt.offsetWidth / tempImage.width;
    var hRatio = caveElmt.offsetHeight / tempImage.height;
    setRatio(
        Math.min(
            (wRatio > 1) ? 1 : wRatio,  //不能比 100% 還大,所以最多壓成 1
            (hRatio > 1) ? 1 : hRatio
        )
    );
    zk.Widget.$("$scale").setCurpos(ratio * 100);
}

function resize() {
    var imageElmt = $("$scaleImage")[0];
    var tempImage = new Image();
    tempImage.src = imageElmt.src;
    imageElmt.width = tempImage.width * ratio;
    imageElmt.height = tempImage.height * ratio;
}

/** value 的值域理論上是 [0,1] **/
function setRatio(value) {
    ratio = Math.min(1, value); //不能比 100% 還大,所以最多壓成 1
    resize();
}

//其實應該要可以直接 call setRatio(),只是 ZK 我實在無法... Orz
function changeRatio() {
    setRatio(zk.Widget.$("$scale").getCurpos() / 100);
}
]]></script>

2015-04-23

C / C ++ 程式設計師使用 JS 的四個階段

原文網址:http://games.greggman.com/game/the-4-stages-of-a-cc-programmer-using-javascript/

第一階段:三年前

這是什麼愚蠢的 script 語言啦?我才沒有用過咧… 但是它是一個 script 語言、然後 script 語言就是爛。除了作 form 驗證之外根本沒啥鳥用。誰在乎阿?我永遠不會用它… 除非我的個人網站打算防止 submit 兩次之類的事情。

第二階段:現在

唉… 這個 project 必須用 JS 作一些東西。幹他媽的爛語言,全域變數是什麼鬼?大括號不能定義變數 scope 又是哪招?好吧,至少我還找到一些愚蠢的方法可以弄出 class 跟繼承行為。

第三階段:三~五年間

哇靠… 如果到處都用 closure、而且用對方法,JS 實在超有趣的你都不知道。非同步的功能直接可以用、所有 API 都是這樣運作的。我可以把一個東西放到螢幕上而不需要 14 個 library、也不用搞了一個禮拜才找到取得一個 window 並顯示出來的方法。我不用搞清楚如何在 7 個不同的平台上用 17 個語言顯示字型。我可以下載圖片、用 canvas 跟 WebGL 畫出東西、播放聲音、存取攝影機跟麥克風… 都花不了什麼功夫。只要 refresh 馬上可以得到回應。外觀設計就丟給設計師,我不用寫一卡車的程式碼;要展示東西給別人看也只要丟個連結給他就好。這實在是太棒啦~

第四階段:五年後

我要重新回去寫一些 C / C++ 的程式。靠北阿… 沒辦法 compile 了?為什麼 IDE 沒辦法再載入這個 project 了?應該是有人改了格式?馬的咧… 為什麼不用 libglfobar link(譯註:原文就是這個字,意義不明 @_@)?搞什麼阿… 寫一個泛用的 callback 要 200 行 meta-template 程式碼?幹他媽的為什麼要搞這麼複雜?阿?你想看看發生啥事情喔?抱歉,我沒辦法 compile 成 Mac 版給你耶… 他媽的 C / C++ 去死吧……