2010年8月27日 星期五

App Engine 上的時區問題

在 App Engine 上如果要儲存時間,在 model 中就會用 DateTimeProperty, DateProperty 或 TimeProperty 來做為資料型態,比如說:
...
from google.appengine.ext import db

class FooModel(db.Model):
  ...
  ...
  created = db.DateTimeProperty(auto_now_add=True)
DateTimeProperty 來說,它對應到的資料結構是 python 標準函式庫中的 datetime.datetime,不過,當你加上了 auto_now_add=True 這個設定之後,每當建立一個新的 entity 時,Datastore API 就會自動加上「現在時間」,也就是 datetime.datetime.now() 的資料,但是 App Engine 所使用的預設時區是 UTC(也就是 GMT+0),簡單地說,如果你的網站使用者都是台灣的用戶,那就會感覺到這個時間晚了八個小時(因為台灣的時區是 CST,也就是 GMT+8),如果要處理這個問題,這裡提供兩個可能的解決方法:

不要依賴自動產生的時間,DIY

第一個解決方法,就是在產生/儲存 entity 時不要讓 Datastore API 幫你產生現在的時間,每次在產生或是更新 entity 資料時,自己手動產生出正確的時間。在解決問題之前,首先要製作一個台灣時區的 tzinfo,才可以去改變 datetime.datetime 所表示的時區:

...
from datetime import tzinfo, timedelta

class TaiwanTimeZone(tzinfo):
    def utcoffset(self, dt):
        return timedelta(hours=8)

    def tzname(self, dt):
        return 'CST'

    def dst(self, dt):
        return timedelta(hours=0)

接著,在產生或更新 entity 資料時:
...
foo = FooModel(......, 
               created=datetime.datetime.now(TaiwanZone()))
foo.put()
這樣就會用台灣時區(GMT+8)的時間來儲存資料了。

在輸出時用 filter 換掉

另外一個方式,就是在 template 要輸出時再換掉時區,當然還是要準備一個 TaiwanTimeZonetzinfo 類別,然而在儲存資料時,還是使用預設的 UTC 時區去處理,但是可以自訂一個 template filter (詳細的作法請見這篇文章)在 template 中顯示時動態換掉時間及時區:
# get template register
register = webapp.template.create_template_register()

@register.filter
def twtz(value):
    from datetime import datetime, timedelta
    return (value + timedelta(hours=8)).replace(tzinfo=TaiwanTimeZone())
這樣只要在 template 中加上這個 filter 來顯示就可以了。

2010年8月21日 星期六

自訂範本系統中的 filter

在 App Engine 上製作網頁時,若是沒有使用 django 或是其它的 template 函式庫,應該都會直接使用 App Engine 所提供的 django template wrapper(用的是 django 0.96 版的 template),不過這就沒辦法(很簡單地)照著 django 所提供的方式自訂標籤及 filter。不過 App Engine 還是有提供自訂 filter 的方式,只要按照下列的步驟(假設你應用程式的目錄是在 $APP):
  1. 建立一個 Python 模組來定義 filters:
    # $APP/my_filters.py
    from google.appengine.ext import webapp
    
    # 取得 template filters register
    register = webapp.template.create_template_register()
    
    @register.filter
    def tolower(string_value):
        return string_value.lower()
    
    這樣一來便建立了一個自訂的 filter: tolower,等等便可以用在 template 中。
  2. 雖然建立好了自訂的 filter(s),但是 App Engine 的 template 函式庫還不知道有這個東西的存在,所以在使用範本引擎輸出前,記得加入下列的程式碼註冊你自訂的 module(s):
    ...
    from google.appengine.ext.webapp import template
    
    # 註冊自訂 filters 的模組(載入模組的名稱)
    template.register_template_library('my_filters')
    
  3. 完成以上步驟後,就可以在範本中像這樣來使用自訂的 filters:
    ...
    Description: {{ description|tolower }}
    

如此一來,便不必在 request handler 中預先處理輸出的內容,可以把這部份的程式碼用 filter 來解決。