(十三)Flask 学习 —— 日期和时间

日期和时间

善意提醒

对于那些尚未注意到的读者,近来项目已经迁移到 github 上,大家能够在这个位置找到代码: https://github.com/miguelgrinberg/microbloghtml

我已经添加了标签指向每一个教程步骤,为您提供便利。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

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

这里是一些不一样格式渲染的效果:

_images/7.jpg

除了提供了 format(),它还提供了 fromNow() 和 calendar():

_images/8.jpg

请注意,在全部的例子中,服务器渲染相同的 UTC 时间,在本身的网页浏览器中执行不一样的渲染。

咱们如今缺乏的就是让 moment 返回的字符串在页面上可见。实现这个最简单的方式就是 Javascript 的 document.write 函数:

<script>
document.write(moment("2012-12-31T23:55:13 Z").format('LLLL'));
</script>

整合 moment.js

在咱们的应用程序中使用 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> 标签是不可能到达到客户端,由于转义成 &lt;script&gt;。把字符串包裹在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

相关文章
相关标签/搜索