對於那些尚未注意到的讀者,近來項目已經遷移到 github 上,大家能夠在這個位置找到代碼: https://github.com/miguelgrinberg/microblog。html
我已經添加了標籤指向每一個教程步驟,爲您提供便利。python
咱們 microblog 應用程序已經忽略很長時間的一個方面就是時間和日期的顯示。git
到目前爲止,咱們信任 Python 自己去渲染在咱們 User 和 Post 對象中的 datetime 對象,然而這真不是一個好的解決方案。github
考慮以下的例子。我在 2012.12.31 的下午 3:54 寫的這篇文章。個人時區是 PST (或者 UTC-8 若是你更喜歡的話)。在 Python 解釋器運行能獲得以下信息:數據庫
>>> from datetime import datetime >>> now = datetime.now() >>> print now2 012-12-31 15:54:42.915204 >>> now = datetime.utcnow() >>> print now 2012-12-31 23:55:13.635874
now() 調用返回本地時區的正確的時間,然而 utcnow() 調用返回 UTC 單位的時間。flask
所以用哪一個更好?瀏覽器
若是咱們全部的時間戳都使用 now(),咱們將會在數據庫中存儲服務器運行本地的時間,然而這會帶來一些問題。安全
有一天咱們想把服務器遷移到不一樣時區的地方,那麼全部數據庫中的時間戳必須修改爲當地的正確時間在服務器重啓以前。服務器
可是這還有一個更重要的問題。對於不一樣時區的用戶很難清楚知道 blog 的發佈時間由於他們看到的是在 PST 時區的時間。他們須要提早知道服務器的時區是 PST 才能作出適當的調整。app
顯然,這不是一個好的選擇,這是爲何開始使用咱們的數據庫的時候,咱們決定咱們老是以 UTC 時區存儲時間戳。
儘管用 UCT 時區標準化了時間戳解決了遷移服務器的問題。可是它解決不了第二個問題,日期和時間在世界上的任何地方都會以 UTC 形式呈獻給用戶。
這也是很使人困惑的。想象下一個在 PST 時區的用戶在下午 3:00 發佈一篇 blog,結果首頁顯示的時間是下午 11:00 或者是 23:00。用戶會不會感到很奇怪啊?
今天,文章的目的是解決日期和時間顯示的問題,使得咱們的用戶不要混淆。
最明顯解決問題的方案就是爲每個用戶單獨把時間戳從 UTC 轉換到當地時區。這也容許咱們接着在數據庫中使用 UTC 時區。
可是如何知道每個用戶的當地時區了?
許多網頁都會有一個配置頁,用戶能夠指定當地時區。這就須要咱們添加一個新頁,新頁上須要一個表單,用戶能夠在表單上的下拉框中選擇本身當地的時區。做爲註冊的一部分,用戶第一次訪問頁面的時候,須要被要求輸入他們的時區。
儘管這是一個比較好的解決方案,可是用戶的系統配置中已經配置了時區,讓用戶輸入這樣的信息有些累贅。若是咱們能獲取用戶電腦上的時區的話看起來更有效些。
出於安全緣由,網頁瀏覽器是不容許從用戶的操做系統中獲取信息。即便這是可能的話,咱們須要在 Windows, Linux, Mac, iOS, 以及 Android 上(這仍是沒有統計其餘類型的操做系統)去查詢時區。
其實網頁瀏覽器知道用戶的時區,而且能夠經過標準的 Javascript APIs 導出。在 Web 2.0 世界裏,用戶開啓 Javascript 的假設是安全的(現代的網頁沒有腳本是不能工做的),所以這個方案是有潛力的。
咱們有兩種方式經過 Javascript 來利用可用的時區配置:
「老派」 的方式就是讓網頁瀏覽器在某時候發送時區信息當用戶第一次登陸到服務器。這能夠經過 Ajax 調用,或者 一個 刷新標記。一旦服務器知道了時區會把它保存到用戶的會話中,當模板渲染的時候會去調整時間。
「新派」 的方式就是不會在服務器上改變任何東西,會繼續把 UTC 時間戳發送到客戶端瀏覽器上。從 UTC 到當地時區的轉換是經過 Javascript 在客戶端實現的。
兩種方式都不錯,可是第二種更加不錯。瀏覽器是最有能力根據系統時區配置來渲染時間的。最具備吸引力的是,第二種方式有現成的。
Moment.js 是一個小型的,自由的,開源的 Javascript 庫,它可以渲染日期和時間。它提供了富有想象的格式化選項。
爲了在咱們應用程序中使用 moment.js,咱們須要在模板中寫一點 Javascript。咱們開始從 ISO 8601 時區構建一個 moment 對象。例如,咱們能夠建立一個 moment 對象像這樣:
moment(「2012-12-31T23:55:13 Z」)
一旦對象被構建,它可以被渲染成各類格式的字符串。例如,根據系統時區進行詳細的渲染:
moment("2012-12-31T23:55:13 Z").format('LLLL');
下面就是結果:
Tuesday, January 1 2013 7:55 AM
這裏是一些不一樣格式渲染的效果:
除了提供了 format(),它還提供了 fromNow() 和 calendar():
請注意,在全部的例子中,服務器渲染相同的 UTC 時間,在本身的網頁瀏覽器中執行不一樣的渲染。
咱們如今缺乏的就是讓 moment 返回的字符串在頁面上可見。實現這個最簡單的方式就是 Javascript 的 document.write 函數:
<script> document.write(moment("2012-12-31T23:55:13 Z").format('LLLL')); </script>
在咱們的應用程序中使用 moment.js 還須要作一些事情。
首先,把下載下來的 moment.min.js 放入到 /app/static/js 文件夾,這樣它就能做爲一個靜態文件提供給客戶端。
接着,在咱們的基礎模板中添加對這個庫的引用(文件 app/templates/base.html):
<script src="/static/js/moment.min.js"></script>
咱們如今在模板中添加 <script> 標籤,用來顯示時間戳。可是與這樣方式不一樣的,咱們將會建立一個 moment.js 封裝,咱們能在模版中調用這個封裝。這會節省很多時間若是咱們必須修改時間戳渲染代碼,由於咱們已經把它放在一個地方。
咱們的封裝是一個很簡單的 Python 類(文件 app/momentjs.py):
from jinja2 import Markup class momentjs(object): def __init__(self, timestamp): self.timestamp = timestamp def render(self, format): return Markup("<script>\ndocument.write(moment(\"%s\").%s);\n</script>" % (self.timestamp.strftime("%Y-%m-%dT%H:%M:%S Z"), format)) def format(self, fmt): return self.render("format(\"%s\")" % fmt) def calendar(self): return self.render("calendar()") def fromNow(self): return self.render("fromNow()")
注意 render 方法並不直接返回字符串而是把它放入了 Jinja2 提供的 Markup 對象中。緣由是 Jinja2 默認狀況下會自動轉義,例如,咱們的 <script> 標籤是不可能到達到客戶端,由於轉義成 <script>。把字符串包裹在Markup 對象裏就是告訴 Jinja2 這個字符串是不須要轉義的。
既然咱們有了一個封裝的類,咱們須要跟 Jinja2 綁定,這樣模塊就可使用它(文件 app/__init__.py):
from momentjs import momentjs app.jinja_env.globals['momentjs'] = momentjs
上面的代碼就是告訴 Jinja2 導入咱們的類做爲全部模板的一個全局變量。
如今咱們準備修改模版。在咱們應用程序中有兩個地方顯示日期和時間。一個就是用戶信息頁,那裏有最後一次登陸時間。對於這個時間戳,咱們將會使用 calendar() 格式(文件 app/templates/user.html):
{% if user.last_seen %} <p><em>Last seen: {{momentjs(user.last_seen).calendar()}}</em></p> {% endif %}
第二個地方就是在 post 子模板,它是被首頁,用戶信息頁以及搜索頁調用。在這裏咱們將會使用 fromNow() 格式,由於一篇 blog 的撰寫時間和它離如今有多久了是同樣重要的。咱們須要修改子模板使得全部使用它的頁面都有效(文件 app/templates/post.html):
<p><a href="{{url_for('user', nickname = post.author.nickname)}}">{{post.author.nickname}}</a> said {{momentjs(post.timestamp).fromNow()}}:</p> <p><strong>{{post.body}}</strong></p>
作完這些修改後,咱們解決了全部咱們的時間戳問題。咱們不須要對服務器代碼作單個的修改!
不知不覺中咱們已經作了很重要的一步,使得 microblog 讓國際用戶可以根據本地時區看到不一樣的日期和時間。在下一章中,咱們會讓國際用戶更加高興,咱們讓 microblog 支持多語言。
若是你想要節省時間的話,你能夠下載 microblog-0.13.zip。