2016年4月25日 星期一

Java : SimpleDateFormat、TimeZone和Date -- 觀念釐清

在這篇裡主要紀錄了在Java中,對於SimpleDateFormat、TimeZone和Date的理解,釐清之前對它們有誤會及不太清楚的地方。

java.util.Date是一個早期Java中專門用來紀錄日期的一個類別,同時也是常用java.sql.Date的父類別,它並沒有儲存時區的資訊,當我們要對其進行不同時區的值(String或Date)轉換時,可以利用SimpleDateFormat及TimeZone來幫助我們。

TimeZone是個儲存時區資訊的類別,而SimpleDateFormat是一個用來進行日期格式轉換的類別,可以利用SimpleDateFormat.setTimeZone()來設定TimeZone,而常用的為以下兩個方法:


  1. String SimpleDateFormat.format(Date)
  2. Date SimpleDateFormat.parse(String)

SimpleDateFormat.format(Date)
可以送入一個Date物件,並返回一個表示日期的String。
如果SimpleDateFormat沒有設定TimeZone的話,
等同設定了TimeZone.getDefault()。
SimpleDateFormat.format(Date) 會將送入的Date參數轉成用設定的TimeZone來看時,正確的日期String。
例如:
如果 Date 是 2022-01-01T23:00:00+08
Timezone 設定 UTC-08
那 format() 出來的 String 就會是
2022-01-01T07:00:00

如果你的 SimpleDateFormat 建構子的字串有設定代表時區的 Z 符號的話,就可以印出時區,
例如:
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
上例的結果會是 2022-01-01T07:00:00-08

SimpleDateFormat.parse(String) 
可以送入一個表示日期的String,並返回一個Date物件。
如果SimpleDateFormat沒有設定TimeZone的話,
等同設定了TimeZone.getDefault()。
SimpleDateFormat.parse(String) 會將送入的 String 轉成當用設定的 Timezone 來看時
正確的 Date 物件。
例如:
如果 String 是 2022-01-01T07:00:00
Timezone 設定 UTC+08
那 parse() 出來的 Date 就會是 
2022-01-01T07:00:00+08

========================
接著下面舉一個例子:

如果我們有一個表示Local TimeZone(假設程式放在台灣server上)日期的Date物件,例如:
System.our.println(dateObject_local);   //2016/4/25 16:47:59


我們想把它轉成在TimeZone為America/Los_Angeles時,看到這個日期應看到的Date物件,例如
System.our.println(dateObject_America);   //2016/4/25 01:47:59


應該要如何做呢?
首先宣告TimeZone並設定給SimpleDateFormat
TimeZone timeZone_America = TimeZone.getTimeZone("America/Los_Angeles");
TimeZone timeZone_default = TimeZone.getDefault();

SimpleDateFormat simpleDateFormat_America = new SimpleDateFormat();
simpleDateFormat_America.setTimeZone(timeZone_America);

SimpleDateFormat simpleDateFormat_Taiwan = new SimpleDateFormat();
simpleDateFormat_Taiwan.setTimeZone(timeZone_default);

Date dateObject_local = new Date();
System.out.println("dateObject_local:" + dateObject_local);  //dateObject_local:Mon Apr 25 18:11:27 CST 2016

接著我們用simpleDateFormat_America去format上面那個Date,
String dateString_America = simpleDateFormat_America.format(dateObject_local);
System.out.println("dateString_America:" + dateString_America); //dateString_America:2016/4/25 上午 3:11

會看到時間被減了15小時,這正是美國比台灣慢15小時的時差。
如果我們想得到輸出是時間 3:11的Date物件,下面這樣是錯的:
Date Wrong_dateObject = simpleDateFormat_America.parse(dateString_America);
System.out.println("Wrong_dateObject:" + Wrong_dateObject); //Wrong_dateObject:Mon Apr 25 18:16:00 CST 2016

原因是當用simpleDateFormat_America去parse上面那個String, dateString_America時,它會用simpleDateFormat_America設定的TimeZone去看,也就是說,它認為這是美國時間3:11,所以要parse回local time的Date時,它會將時間加回15小時,所以得到非我們預期的結果。

正確的做法是下面這樣:
Date Correct_dateObject = simpleDateFormat_Taiwan.parse(dateString_America);
System.out.println("Correct_dateObject:" + Correct_dateObject); //Correct_dateObject:Mon Apr 25 03:16:00 CST 2016

我們用simpleDateFormat_Taiwan去parse,讓它以為這是本地時間3:11,再parse成local time的Date時,就不會進行時差的動作了(因為時差為0)。

只要記得,Date本身沒有時區的資訊,基本上都用Local TimeZone去對待得到一個無時區概念的毫秒數,不管是被format還是由parse得到。

而String則會用SimpleDateFormat設定的TimeZone去對待,不管是由format得到還是被parse。

2016年4月18日 星期一

ng-repeat-start和ng-repeat-end -- AngularJS

在AngularJS中,我們常會用ng-repeat這個directive來將一串資料顯示出來,不過ng-repeat的功能是有局限性的,例如如果我們有以下的data,

datas = [{name : 1 , phone:111},{name : 2 , phone:222},{name : 3 , phone: 333}];

並想讓以下重覆顯示的話,ng-repeat就不夠用了。

<h1>Name : {{data.name}}</h1>
<p style="border-bottom:thick solid #ff0000;">Phone : {{data.phone}}</p>

這時我們可以使用ng-repeat-start及ng-repeat-end,

在ng-repeat-start標示的tag及ng-repeat-end標示的tag之間(包括被ng-repeat-start和ng-repeat-end標示的tag),能夠存取到同一個串列資料中的各元素,下面就是一個使用的簡單例子:


我們先定義了ng-app、ng-controller及其中的datas。

var myapp = angular.module('myapp',[]);
myapp.controller('Ctrl',[function(){
 var self = this;
  self.datas = [{name : 1 , phone:111},{name : 2 , phone:222},{name : 3 , phone: 333}];
}]);

接著在設計要顯示的html:

<div ng-app="myapp" ng-controller="Ctrl as ctrl">  
  <h1 ng-repeat-start = "data in ctrl.datas">Name : {{data.name}}</h1>
  <p ng-repeat-end style="border-bottom:thick solid #ff0000;">Phone : {{data.phone}}</p>  
</div>

可以看到每一次的repeat都從串列資料(datas)中取出一組資料(包括了name及phone的資訊),並顯示一組
<h1>......</h1>
<p>.......</p>

參考資料:

  1. ngRepeat

2016年4月13日 星期三

AngularJS的$compile() - 動態增加DOM

在AngularJS中,會在瀏覽器對html解析成DOM後,先對AngularJS(ng-app)的進行編譯,給定scope後才會進行數據綁定。

但是如果是用例如JQuery等方式新增了新的DOM元素後,因為沒有用AngularJS編譯的關係,所以就算裡面有AngularJS的語法也不會呈現預期的結果。

例如像以下的例子:

HTML:
<div ng-app="myApp">
  <div id='DOMWrapper' ng-controller="myCtrl">
    <button ng-click='addDOM()'>Add DOM</button>
  </div>
</div>
Javascript:
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', '$compile', function($scope, $compile) {
	$scope.textOfDOM = 'Text show successfully';

  $scope.addDOM = function() {
    var DOMToAdd = $('<p>{{textOfDOM}}</p>');
    
    //Wrong code -- start
    $('#DOMWrapper').append(DOMToAdd);
    //Wrong code -- end    
    
    //Right code -- start
    //var linkOfDOMToAdd = $compile(DOMToAdd);
    //var nodeOfCompiledDOM = linkOfDOMToAdd($scope);
    //$('#DOMWrapper').append(nodeOfCompiledDOM);
    //Right code -- end
  };
}]);

 從結果中可以看到當按下"Add DOM"按鈕時,出現的字是 "{{textOfDOM}}",而不是我們預期的 "Text show successfully"。

這是因為要新增的DOM沒有被AngularJS編譯的關係,以下才是正確的程式碼:
HTML:
<div ng-app="myApp">
  <div id='DOMWrapper' ng-controller="myCtrl">
    <button ng-click='addDOM()'>Add DOM</button>
  </div>
</div>
Javascript:
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', '$compile', function($scope, $compile) {
	$scope.textOfDOM = 'Text show successfully';

  $scope.addDOM = function() {
    var DOMToAdd = $('<p>{{textOfDOM}}</p>');
    
    //Wrong code -- start
    //$('#DOMWrapper').append(DOMToAdd);
    //Wrong code -- end
    
    //Right code -- start
    var linkOfDOMToAdd = $compile(DOMToAdd);
    var nodeOfCompiledDOM = linkOfDOMToAdd($scope);
    $('#DOMWrapper').append(nodeOfCompiledDOM);
    //Right code -- end
  };
}]);
我們可以呼叫$compile()並把要編譯的DOM送進去當參數,它會返回一個 link 函式,呼叫 link 函式並把scope當作輸入參數,就可以將scope與被編譯的DOM產生連結,所以DOM就可以得到textOfDOM的值,link 函式會返回已經跟資料連結好的新的DOM物件,接著我們再把新的DOM物件用JQuery的語法放到 #DOMWrapper裡面,就可以正確的得到我們要的 "Text show successfully"了。

參考資料:

  1. 18.5. Compile的细节
  2. $compile