英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xiii-dates-and-times
中文翻译地址:http://www.pythondoc.com/flask-mega-tutorial/dateandtime.html备注:开源社区的好像是只翻译到十二部分,剩下的我就找不到了。
一、时间戳问题
到目前为止,我们使用 Python 本身去渲染在我们 User 和 Post 对象中的 datetime 对象,但并不是完全没有问题的。
now() 调用返回本地时区的正确的时间,然而 utcnow() 调用返回 UTC 单位的时间。
UTC是Coordinated Universal Time- 世界协调时间,又称世界统一时间,中国时区是CST,UTC+8
如果我们所有的时间戳都使用 now(),我们将会在数据库中存储服务器运行本地的时间。如果有一天我们想把服务器迁移到不同时区的地方,那么所有数据库中的时间戳必须在服务器重启之前修改成当地的正确时间。
而且不同时区的用户很难清楚知道 blog 的发布时间,因为他们看到的是在服务器所在时区的时间。他们需要提前知道服务器的时区是什么时区才能做出适当的调整。
用 UCT 时区标准化了时间戳解决了迁移服务器的问题。但是日期和时间在世界上的任何地方都会以 UTC 形式呈献给用户。这会显得非常奇怪。
二、用户特定的时间戳
最解决问题的方案就是为每一个用户单独把时间戳从 UTC 转换到当地时区。这也允许我们接着在数据库中使用 UTC 时区。
知道用户时区的方法,可以让用户注册的时候选择时区,不过比较累赘。如果能知道电脑上的时间会方便点,不过出于安全原因这是不被允许的。
其实网页浏览器知道用户的时区,并且可以通过标准的 Javascript APIs 导出。有两种方式通过 Javascript 来利用可用的时区配置:
- “老派” 的方式就是让网页浏览器在某时候发送时区信息当用户第一次登录到服务器。这可以通过 Ajax 调用,或者 一个 刷新标记。一旦服务器知道了时区会把它保存到用户的会话中,当模板渲染的时候会去调整时间。
- “新派” 的方式就是不会在服务器上改变任何东西,会继续把 UTC 时间戳发送到客户端浏览器上。从 UTC 到当地时区的转换是通过 Javascript 在客户端实现的。
两种方式都不错,但是第二种更加不错。浏览器是最有能力根据系统时区配置来渲染时间的。更有吸引力的是,第二种方式有现成的。
三、介绍 moment.js
Moment.js 是一个小型的,自由的,开源的 Javascript 库,它能够渲染日期和时间。它提供了富有想象的格式化选项。
下载以及介绍直接点链接看官网就好了,很清晰。
四、整合使用moment.js
首先,把下载下来的 moment.min.js 放入到 /app/static/js 文件夹,这样它就能作为一个静态文件提供给客户端。(我下载下来的文件名字是moment.js)
接着,在我们的基础模板中添加对这个库的引用(文件 app/templates/base.html):
<script src="/static/js/moment.min.js"></script>
接下来我们可以在模板里面添加 <script> 标签,写相关操作。不过把它放到一个文件封装起来,然后在文件中调用会更好。
我们的封装是一个很简单的 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>
效果:
备注:
其实我觉得如果熟悉前端的,也许直接写js会更简单一点?