天天看点

12 Flask mega-tutorial 第12章 日期和时间 Dates and Times时区“地狱”时区转换介绍Moment.js和Flask-Moment使用Moment.js

如需转载请注明出处。

win10 64位、Python 3.6.3、Notepad++、Chrome 67.0.3396.99(正式版本)(64 位)

注:作者编写时间2018-02-21,linux、python 3.5.2

以下内容均是加入自己的理解与增删,以记录学习过程。不限于翻译,部分不完全照搬作者Miguel Grinberg的博客,版权属于作者,感谢他提供免费学习的资料。

传送门
00 开篇 01 Hello world 02 模板 03 Web表单
04 数据库 05 用户登录 06 个人资料和头像 07 错误处理
08 关注 09 分页 10 支持QQ邮箱 11 美化页面
12 时间和日期 13 I18n和L10n 翻译成中文 zh-CN 14 Ajax(百度翻译API 15 更好的App结构(蓝图)
16 全文搜索 17 部署到腾讯云Ubuntu 18 部署到Heroku 19 部署到Docker容器
20 JavaScript魔法 21 用户通知 22 后台工作(Redis) 23 应用程序编程接口(API)

本章将学习如何以适合所有用户的方式使用

日期和时间

,无论他们身居何处。

目前为止,一直忽略Microblog应用程序显示日期和时间的问题,只是让

Python

渲染了

User

模型中的

datetime

对象,并完全忽略

Post

模型中的

datetime

对象。

时区“地狱”

在服务器上用

Python

去呈现日期和时间,在Web浏览器上以这种方式渲染给用户可不是一个好主意。如示例,在Python解释器中运行如下内容:

(venv) D:\microblog>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> str(datetime.now())
'2018-08-23 09:52:14.895893'
>>> str(datetime.utcnow())
'2018-08-23 01:52:28.247986'
>>> quit()

(venv) D:\microblog>
           

调用

datetime.now()

将返回我当前所在地区(中国-北京(时间))的正确的时间,而调用

datetime.utcnow()

将返回UTC时区的时间。如果我能让生活在世界不同地区的许多人同时一起运行上述代码,

datetime.now()

函数将为每个人返回不同的结果,但

datetime.utcnow()

无论地理位置在哪,都将返回相同的时间。那么你认为哪一个更适合一个很可能让用户遍布全球的Web应用程序中使用?

很明显,服务器必须管理 一致且独立于位置的时间。如果这个应用程序增长到世界各地需要多个

生产服务器

的程序,当然就不希望每个服务器在不同时区写入数据库的时间戳,因为这样就无法使用这些时间。由于

UTC

是最常用的统一时区,并且在

datetime类

中受支持,因此在此将使用它。

但这种方法存在一个重要问题。对于不同时区的用户,如果他们在UTC看到时间,那么将很难弄清楚发布帖子的时间。他们需要提前时间是UTC,以便他们能够“精神上”调整时间到他们自己的时区。想象一下,PDT时区的用户在下午3点发布了一些内容,并立即看到这个帖子在UTC时间晚上10点出现,或更准确的说是22:00。这将是很令人困惑的。

虽然从服务器的角度上,将时间戳标准化很有意义,但这会用户带来可用性问题。本章的目标是解决这个问题,同时保持服务器以UTC格式管理的所有时间戳。

时区转换

这个问题显而易见的解决方案是:将所有时间戳 从存储的UTC单位转换为每个用户的本地时间。这允许服务器继续使用UTC来保持一致性,同时为每个用户量身定制地即时转换解决可用性问题。这个解决方案的棘手部分 是了解每个用户的位置。

许多网站都有一个配置页面,用户可以在其中指定时区。这将要求我添加一个带表单的新页面,这个表单中,我可以向用户显示带有时区列表的下拉列表。作为注册的一部分,用户可以在第一次访问网站时要求输入他们的时区。

虽然这是解决问题的一个不错解决方案,但要求用户输入他们已在其操作系统中配置的信息有点奇怪。如果能从他们的计算机中获取时区设置似乎会更有效率。

事实证明,Web浏览器知道用户的时区,并通过标准日期和时间JavaScript API公开它。实际上,通过JavaScript,有两种方法可利用时区信息:

  1. “老派”方法是在用户首次登陆应用程序时,让Web浏览器以某种方式将时区信息发送到服务器。这可以通过Ajax调用完成,或更简单地使用

    meta refresh tag

    。一旦服务器知道时区,它就可以将其保存在用户的会话中,或将其写入数据库中的用户条目,然后在渲染模板时用它调整所有时间戳。
  2. “新派”方法是不改变服务器中的东西,而在客户端中使用JavaScript在客户端中进行从UTC到本地时区的转换。

这两个选项都是有效的,但第二个选项有很大的优势。光是知道用户的时区,并不足以以用户期望的格式呈现日期和时间。浏览器还可访问系统区域配置,这个配置指定AP/PM与24小时制,DD/MM/YYYY与MM/DD/YYYY,以及许多其他文化或区域样式之类的内容。

如果上述还不够,那么“新派”方法还有一个优势。有一个开源库可完成所有这些工作。

介绍Moment.js和Flask-Moment

[Moment.js](http://momentjs.com/)是一个小型开源JavaScript库,它将日期和时间渲染成另一个级别,因为它提供每一个可想象的格式化选项。不久前,建立了

Flask-Moment

,它是一个小型

Flask扩展

,它可以轻易地将`moment.js`合并到应用程序中。

首先,安装

Flask-Moment

:版本0.6.0

(venv) D:\microblog>pip install flask-moment
Collecting flask-moment
  Using cached https://files.pythonhosted.org/packages/dd/f7/13e9d7480f9097e0efe945e17309c34e0a547a6cfb3f9728324d2f9bf462/Flask_Moment-0.6.0-py2.py3-none-any.whl
Requirement already satisfied: Flask in d:\microblog\venv\lib\site-packages (from flask-moment)
Requirement already satisfied: Jinja2>=2.10 in d:\microblog\venv\lib\site-packages (from Flask->flask-moment)
Requirement already satisfied: itsdangerous>=0.24 in d:\microblog\venv\lib\site-packages (from Flask->flask-moment)
Requirement already satisfied: Werkzeug>=0.14 in d:\microblog\venv\lib\site-packages (from Flask->flask-moment)
Requirement already satisfied: click>=5.1 in d:\microblog\venv\lib\site-packages (from Flask->flask-moment)
Requirement already satisfied: MarkupSafe>=0.23 in d:\microblog\venv\lib\site-packages (from Jinja2>=2.10->Flask->flask-moment)
Installing collected packages: flask-moment
Successfully installed flask-moment-0.6.0
           

以常规方式添加到Flask应用程序中:

app/__init__.py

:添加Flask-Moment实例

# ...
from flask_moment import Moment

app = Flask(__name__)
# ...
moment = Moment(app)
           

与其他扩展不同,

Flask-Moment

moment.js

一起使用,因此应用程序的所有模板都必须包含这个库。为了确保这个库始终可用,将在 基础模板中添加它。这可通过两种方式完成。最直接的方法是显示地以导入库的方式添加一个

<script>

标签,但

Flask-Moment

使其变得更容易,即通过公开一个

moment.include_moment()

函数,它会生成

<script>

标签。

app/templates/base.html

:在基础模板中包含moment.js

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

在这添加的

scripts块

Flask-Bootstrap

的基础模板导出的另一个块。这是包含JavaScript导入的地方。这个块与之前的块不同,因为它已经在基础模板中定义了一些内容。我想的是 添加

moment.js

库,而不会失去基本内容。这是通过

super()

语句实现的,这个语句将保留基础模板中的内容。如果在没有使用

super()

的情况下,在你的模板中定义一个块,那么在基础模板中,为这个块定义的任何内容都将失去。

使用Moment.js

Moment.js

使得一个`moment类`可供浏览器使用。渲染时间戳的第一步是创建这个类的对象,以ISO 8601格式传递所需的时间戳。下方是例子:

t = moment('2017-09-28T21:45:23Z')
           

如果你不熟悉日期和时间的ISO 8601标准格式,格式如:

{{ year }}-{{ month }}-{{ day }}T{{ hour }}:{{ minute }}:{{ second }}{{ timezone }}

。我已经决定只用UTC时区,所以最后一部分将始终是

Z

,它代表ISO 8601标准中的UTC。

moment

对象提供为不同渲染选项提供了几种方法。以下是一些最常见的选项:

moment('2017-09-28T21:45:23Z').format('L')
"09/28/2017"
moment('2017-09-28T21:45:23Z').format('LL')
"September 28, 2017"
moment('2017-09-28T21:45:23Z').format('LLL')
"September 28, 2017 2:45 PM"
moment('2017-09-28T21:45:23Z').format('LLLL')
"Thursday, September 28, 2017 2:45 PM"
moment('2017-09-28T21:45:23Z').format('dddd')
"Thursday"
moment('2017-09-28T21:45:23Z').fromNow()
"7 hours ago"
moment('2017-09-28T21:45:23Z').calendar()
"Today at 2:45 PM"
           

上述示例 创建了一个时刻对象,初始化为

2017年9月28日晚上9:45 UTC

。上面尝试的所有选项都以UTC-7时区(在中国的话,将用UTC+8)来呈现,因为这是作者的计算机上配置的时区。可在浏览器的控制台中输入上述命令,得确保打开控制台的页面包含moment.js。如果引入了moment.js,也可以在Microblog上操作。或者在https://momentjs.com/上操作。

注意不同方法是如何创建不同的表示的。使用

format()

,可以控制字符串的输出格式,类似Python中的

strftime()

函数。

fromNow()

calendar()

方法很有趣,因为它们会根据当前时间显示时间戳,所以会得到如“一分钟前”或“两个小时内”的输出。

如果直接使用JavaScript,那么上述调用将返回具有呈现时间戳的字符串。然后,可将此文本插入页面上的适当位置,遗憾的是,需要JavaScript与DOM配合使用。

Flask-Moment

在模板中启用类似于JavaScript的对象,极大地简化

moment.js

的使用。

我们来看一下 个人资料页面 中显示的时间戳。当前

user.html

模板允许使用Python生成时间的字符串表示。现在使用

Flask-Moment

渲染这个时间戳,如下:

app/templates/user.html

:使用moment.js渲染时间戳

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

使用的语法类似于 JavaScript库的语法,一个区别是

moment()

的参数现在是一个Python

datetime

对象,而不是一个ISO 8601字符串。

moment()

从模板发出的调用还会自动生成所需的JavaScript代码,以将呈现的时间戳插入到DOM的适当位置。

可以利用

Flask-Moment

moment.js

的第二个地方是

_post.html

子模板,它是从

/index

/user

页面中调用的。在当前版本的模板中,每个帖子前面都有一个

“username says:”行

。现在添加一个

fromNow()

时间戳:

app/templates/_post.html

<a href="{{ url_for('user', username=post.author.username) }}" target="_blank" rel="external nofollow" >
                    {{ post.author.username }}
                </a>
                said {{ moment(post.timestamp).fromNow() }}:
                <br>
                {{ post.body }}
           

flask run

运行程序,效果:

C:\Users\Administrator>d:

D:\>cd D:\microblog\venv\Scripts

D:\microblog\venv\Scripts>activate
(venv) D:\microblog\venv\Scripts>cd D:\microblog

(venv) D:\microblog>set MAIL_SERVER=smtp.qq.com

(venv) D:\microblog>set MAIL_PORT=465

(venv) D:\microblog>set MAIL_USE_SSL=True

(venv) D:\microblog>set [email protected]

(venv) D:\microblog>set MAIL_PASSWORD=16位授权码

(venv) D:\microblog>flask run
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
[2018-08-23 15:10:27,042] INFO in __init__: Microblog startup
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [23/Aug/2018 15:10:36] "GET /user/oldiron HTTP/1.1" 200 -
127.0.0.1 - - [23/Aug/2018 15:11:04] "GET /edit_profile HTTP/1.1" 200 -
127.0.0.1 - - [23/Aug/2018 15:11:05] "GET /index HTTP/1.1" 200 -
127.0.0.1 - - [23/Aug/2018 15:11:08] "GET /user/oldiron HTTP/1.1" 200 -
127.0.0.1 - - [23/Aug/2018 15:11:21] "GET /explore HTTP/1.1" 200 -
           
12 Flask mega-tutorial 第12章 日期和时间 Dates and Times时区“地狱”时区转换介绍Moment.js和Flask-Moment使用Moment.js

目前为止,项目结构:

microblog/
    app/
        templates/
	        email/
		        reset_password.html
		        reset_password.txt
	        _post.html
	        404.html
	        500.html
            base.html
            edit_profile.html
            index.html
            login.html
            register.html
            reset_password.html
            reset_password_request.html
            user.html
        __init__.py
        email.py
        errors.py
        forms.py
        models.py
        routes.py
    logs/
        microblog.log
    migrations/
    venv/
    app.db
    config.py
    microblog.py
    tests.py
      

参考:

作者博客

源代码

如需转载请注明出处。

继续阅读