12.日期和時(shí)間

在本章绎谦,我們將討論怎么用適配不同時(shí)區(qū)的所有用戶的方式來處理日期和時(shí)間,無論他們身處地球上的何處粥脚。

顯示日期和時(shí)間是 Microblog 應(yīng)用中長期被忽略的其中一個(gè)方面窃肠。直到現(xiàn)在,我也只是讓 Python 渲染了 User 模型中的 datetime 對(duì)象刷允,并且完全忽略了 Post 模型中的 datetime 對(duì)象冤留。






時(shí)區(qū)地域

我們先來 Python shell 中看看這個(gè)例子:

>>> from datetime import datetime
>>> str(datetime.now())
'2021-08-02 17:09:16.366550'
>>> str(datetime.utcnow())
'2021-08-02 09:09:23.533968'

datetime.now() 調(diào)用返回我所處位置的本地時(shí)間,而 datetime.utcnow() 調(diào)用則返回 UTC 時(shí)區(qū)(協(xié)調(diào)世界時(shí))中的時(shí)間树灶。假設(shè)世界不同地區(qū)的多人同時(shí)運(yùn)行上面的代碼纤怒,datetime.now() 函數(shù)將為每個(gè)人返回不同的結(jié)果,但是無論身在哪個(gè)時(shí)區(qū)天通,datetime.utcnow() 總是會(huì)返回同一時(shí)間泊窘。那么你認(rèn)為哪一個(gè)更適合用在一個(gè)用戶遍布世界各地的 Web 應(yīng)用中呢?

很明顯,服務(wù)器必須管理一致且獨(dú)立于位置的時(shí)間烘豹。我們肯定不希望每個(gè)用戶都在寫入不同時(shí)區(qū)的時(shí)間戳到數(shù)據(jù)庫瓜贾,因?yàn)檫@會(huì)導(dǎo)致其無法正常地運(yùn)行。 UTC 是最常用的統(tǒng)一時(shí)區(qū)携悯,并且在 datetime 類中受到支持祭芦,因此我將會(huì)使用它。

現(xiàn)在可以得出結(jié)論憔鬼,對(duì)處于不同時(shí)區(qū)的用戶龟劲,我們需要知道它的 UTC時(shí)間和所在的時(shí)區(qū),再通過時(shí)區(qū)轉(zhuǎn)換的計(jì)算轴或,從而正確地為用戶顯示當(dāng)?shù)馗袷降臅r(shí)間昌跌。






時(shí)區(qū)轉(zhuǎn)換

該問題的直接解決方案是將所有時(shí)間戳從存儲(chǔ)的 UTC 單位轉(zhuǎn)換為每個(gè)用戶的本地時(shí)間。這樣一來照雁,服務(wù)器可以繼續(xù)使用 UTC 來保持時(shí)區(qū)的一致性蚕愤,而針對(duì)每個(gè)用戶量身定制的即時(shí)轉(zhuǎn)換來解決可用性問題。這個(gè)解決方案棘手的部分是要知道每個(gè)用戶的位置囊榜。

其中一個(gè)解決方法是要求用戶在個(gè)人資料處選擇他們的時(shí)區(qū)审胸。這將需要為此添加一個(gè)新的功能亥宿。也可以要求用戶在第一次訪問網(wǎng)站時(shí)卸勺,作為注冊(cè)的一部分,會(huì)被要求選擇他們的時(shí)區(qū)烫扼。

雖然該方案可以解決問題曙求,但要求用戶選擇他們已經(jīng)在其操作系統(tǒng)中配置的信息有點(diǎn)奇怪。 如果我能從他們的計(jì)算機(jī)操作系統(tǒng)中獲取時(shí)區(qū)信息映企,似乎效率會(huì)更高悟狱。

其實(shí)通過 Web 瀏覽器就可以獲取用戶的時(shí)區(qū),并通過標(biāo)準(zhǔn)的日期和時(shí)間 JavaScript API 暴露它堰氓。 有兩種方法來利用 JavaScript 提供的時(shí)區(qū)信息:

  • “老派”方法是當(dāng)用戶第一次登錄到應(yīng)用程序時(shí)挤渐,Web 瀏覽器以某種方式將時(shí)區(qū)信息發(fā)送到服務(wù)器。這可以通過 Ajax 調(diào)用完成双絮,或者更簡單地使 meta refresh浴麻。一旦服務(wù)器知道了時(shí)區(qū),就可以將其保存在用戶的會(huì)話中囤攀,或者將其寫入用戶在數(shù)據(jù)庫中的條目中软免,然后在渲染模板時(shí)從中調(diào)整所有時(shí)間戳。

  • “新派”的做法是不改變服務(wù)器中的東西焚挠,而在瀏覽器端使用 JavaScript 來對(duì) UTC 和本地時(shí)區(qū)之間進(jìn)行轉(zhuǎn)換膏萧。

兩種選擇都是有效的,但第二種選擇有很大優(yōu)勢(shì)。 光是知道用戶的時(shí)區(qū)并不足以以用戶期望的格式呈現(xiàn)日期和時(shí)間榛泛。 瀏覽器還可以訪問系統(tǒng)區(qū)域配置蝌蹂,該配置指定 AM/PM 與 24 小時(shí)制,DD/MM/YYYYMM/DD/YYYY挟鸠,以及許多其他文化或地區(qū)風(fēng)格之類的東西叉信。

如果這還不夠,新派方法還有另一個(gè)優(yōu)勢(shì)艘希,用一個(gè)開源的庫來完成所有這些工作硼身!






Moment.js 和 Flask-Moment 簡介

Moment.js 是一個(gè)小型的 JavaScript 開源庫,它將日期和時(shí)間轉(zhuǎn)換成目前可以想象到的所有格式覆享。而 Flask-Moment佳遂,一個(gè)小型 Flask 插件,它可以使你在應(yīng)用中輕松使用 moment.js撒顿。

我們還是從安裝 Flask-Moment 來開始:

(venv) $ pip install flask-moment

一如既往丑罪,在 app\__init__.py 中初始化插件:

# app\__init__.py

from flask_moment import Moment

moment = Moment()

def create_app(config):
    app = Flask(__name__)
    moment.init_app(app)
    # ...
    return app

與其他插件不同的是,Flask-Moment 需要與 moment.js 一起工作凤壁,因此應(yīng)用的所有模板都必須包含 moment.js吩屹。為了確保該庫始終可用,我將把它添加到基礎(chǔ)模板 base.html 中拧抖,可以通過兩種方式完成煤搜。 最直接的方法是顯式添加一個(gè) <script> 標(biāo)簽來引入庫。

還記得我們上一章講 Flask-Bootstrap 時(shí)候講到可以使用 {% block content %} 塊來插入 Javascript 腳本嗎唧席。結(jié)合 Flask-Momentmoment.include_moment() 函數(shù)擦盾,我們就可以很容易地實(shí)現(xiàn)它,它會(huì)直接生成了一個(gè) <script> 標(biāo)簽并在其中包含 moment.js

# app\templates\base.html

{% block scripts %}
  {{ super() }}
  {{ moment.include_moment() }}
{% endblock %}

我在這里添加的 scripts 塊是 Flask-Bootstrap 基礎(chǔ)模板暴露的另一個(gè)塊淌哟,這是 JavaScript 引入的地方迹卢。該塊與之前的塊不同的地方在于它已經(jīng)在基礎(chǔ)模板中定義了一些內(nèi)容了。我想要追加 moment.js 庫的話徒仓,就需要使用 super() 語句腐碱,才能繼承基礎(chǔ)模板中已有的內(nèi)容,否則就是替換掉弛。






使用 Moment.js

Moment.js 為瀏覽器提供了一個(gè) moment 類症见。呈現(xiàn)時(shí)間戳的第一步是創(chuàng)建此類的對(duì)象,并以 ISO 8601 格式傳遞所需的時(shí)間戳狰晚。

運(yùn)行我們的應(yīng)用后筒饰,我們?cè)跒g覽器打開它,并打開瀏覽器的控制器壁晒,在控制器中瓷们,我們演示一下如何使用 Moment.js

let t = moment('2021-08-02T21:45:23Z')

如果你對(duì)日期和時(shí)間不熟悉 ISO 8601 標(biāo)準(zhǔn)格式,格式如下: {{ year }}-{{ month }}-{{ day }}T{{ hour }}:{{ minute }}:{{ second }}{{ timezone }}谬晕。我因?yàn)槲覀冎皇褂?UTC 時(shí)區(qū)碘裕,因此最后一部分總是將會(huì)是 Z,它表示 ISO 8601 標(biāo)準(zhǔn)中的 UTC攒钳。

moment 對(duì)象為不同的渲染選項(xiàng)提供了幾種方法帮孔。 以下是一些最常見的幾種:

moment('2021-08-02T21:45:23Z').format('L')
"08/03/2021"

moment('2021-08-02T21:45:23Z').format('LL')
"August 3, 2021"

moment('2021-08-02T21:45:23Z').format('LLL')
"August 3, 2021 5:45 AM"

moment('2021-08-02T21:45:23Z').format('LLLL')
"Tuesday, August 3, 2021 5:45 AM"

moment('2021-08-02T21:45:23Z').format('dddd')
"Tuesday"

moment('2021-08-02T21:45:23Z').fromNow()
"in 6 hours"

moment('2021-08-02T21:45:23Z').calendar()
"Tomorrow at 5:45 AM"

此示例創(chuàng)建了一個(gè) moment 對(duì)象,該對(duì)象被初始化為 2017 年 9 月 28 日晚上 9:45 UTC不撑。你可以看到文兢,我上面嘗試的所有選項(xiàng)都以 UTC+8 時(shí)區(qū)來呈現(xiàn),因?yàn)檫@是我計(jì)算機(jī)上配置的時(shí)區(qū)焕檬。你可以在 microblog 上進(jìn)行此操作姆坚,只要你引入了 moment.js∈涤蓿或者你也可以在 https://momentjs.com/ 上嘗試兼呵。

請(qǐng)注意不同的方法是如何創(chuàng)建的不同的表示。使用 format()腊敲,你可以控制字符串的輸出格式击喂,類似于 Python 中的 strftime函數(shù)。fromNow()calendar() 方法很有趣碰辅,因?yàn)樗鼈儠?huì)根據(jù)當(dāng)前時(shí)間顯示時(shí)間戳懂昂,因此你可以獲得諸如“一分鐘前”或“兩小時(shí)內(nèi)”等輸出。

如果你直接在 JavaScript 中運(yùn)行乎赴,則上述調(diào)用將返回渲染后的時(shí)間戳字符串忍法。 然后潮尝,你可以將此文本插入頁面上的適當(dāng)位置榕吼,這需要 JavaScript 與 DOM 配合使用。

Flask-Moment 插件通過啟用一個(gè)類似于 JavaScript 上的 moment 對(duì)象勉失,大大簡化了對(duì) moment.js 的使用羹蚣,并融合了所需的 JavaScript 邏輯,使渲染后的時(shí)間展示在頁面上乱凿。

我們來看看出現(xiàn)在個(gè)人主頁中的時(shí)間戳顽素。 當(dāng)前的 user.html 模板使用 Python 生成時(shí)間的字符串表示。 現(xiàn)在我可以使用 Flask-Moment 渲染此時(shí)間戳:

# app\templates\auth\user.html

{% if user.last_seen %}
  <p>Last seen on: {{ moment(user.last_seen).format('LLL') }}</p>
{% endif %}

如你所見徒蟆,Flask-Moment 使用的語法類似于 JavaScript 庫的語法胁出,其中一個(gè)區(qū)別是,moment() 的參數(shù)現(xiàn)在是 Python 的 datetime 對(duì)象段审,而不是ISO 8601 字符串全蝶。 從模板發(fā)出的 moment() 調(diào)用也會(huì)自動(dòng)生成所需的 JavaScript 代碼,以將呈現(xiàn)的時(shí)間戳插入DOM的適當(dāng)位置。

使用 Flask-Momentmoment.js 的第二個(gè)地方是被主頁和個(gè)人主頁調(diào)用的 _post.html 子模板抑淫。 現(xiàn)在我可以添加一個(gè)用 fromNow() 渲染的時(shí)間戳:

<td>
  <a href="{{ url_for('auth.user', username=post.author.username) }}">
    {{ post.author.username }}
  </a>      
  says: {{ moment(post.timestamp).fromNow() }}
  <br>
  {{ post.body }}
</td>

下面绷落,你可以看到這兩個(gè)時(shí)間戳在 Flask-Momentmoment.js 的渲染下:

現(xiàn)在我們的應(yīng)用已經(jīng)具有根據(jù)用戶系統(tǒng)的時(shí)區(qū)自動(dòng)轉(zhuǎn)換成本地時(shí)間的功能了,不信始苇?可以修改系統(tǒng)時(shí)區(qū)再刷新我們的應(yīng)用試試砌烁。






本文源碼:https://github.com/SingleDiego/Flask-Tutorial-Source-Code/tree/SingleDiego-patch-12

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市催式,隨后出現(xiàn)的幾起案子函喉,更是在濱河造成了極大的恐慌,老刑警劉巖荣月,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件函似,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡喉童,警方通過查閱死者的電腦和手機(jī)撇寞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堂氯,“玉大人蔑担,你說我怎么就攤上這事⊙拾祝” “怎么了啤握?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晶框。 經(jīng)常有香客問我排抬,道長,這世上最難降的妖魔是什么授段? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任蹲蒲,我火速辦了婚禮,結(jié)果婚禮上侵贵,老公的妹妹穿的比我還像新娘届搁。我一直安慰自己,他們只是感情好窍育,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布卡睦。 她就那樣靜靜地躺著,像睡著了一般漱抓。 火紅的嫁衣襯著肌膚如雪表锻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天乞娄,我揣著相機(jī)與錄音瞬逊,去河邊找鬼檐迟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛码耐,可吹牛的內(nèi)容都是我干的追迟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼骚腥,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼敦间!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起束铭,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤廓块,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后契沫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體带猴,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年懈万,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拴清。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡会通,死狀恐怖口予,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涕侈,我是刑警寧澤沪停,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站裳涛,受9級(jí)特大地震影響木张,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜端三,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一舷礼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧技肩,春花似錦且轨、人聲如沸浮声。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泳挥。三九已至塑荒,卻和暖如春勃痴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厂财,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像嫌变,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子躬它,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容