在Python的Flask框架中使用日期和时间的教程


Posted in Python onApril 21, 2015

 时间戳的问题

我们的微博应用的一个忽略了很久的问题就是日间和日期的显示。

直到现在,我们在我们的User和Post对象中使用Python它自己的方式来渲染时间对象,但这并不是一个好的解决方案。

考虑下这样的例子。我正在写这篇文章,此时正是12月31号下午3:54。我的时区是PST(或者你们更习惯的:UTC-8)。 在Python解释器中运行,我得到下面输出:
 

>>> from datetime import datetime
>>> now = datetime.now()
>>> print now
2012-12-31 15:54:42.915204
>>> now = datetime.utcnow()
>>> print now
2012-12-31 23:55:13.635874

在我所在的地方,now()方法返回了正确的时间,但是now()调用返回的时间是UTC单位。

那么,使用哪个更好呢?

如果我们用now(),所有数据库里的时间戳将会与服务器运行的当地时间一致,这将会产生一些问题。

比如,如果有一天,我们需要将服务器放到别的地方(不在一个时区),那么在重启服务器之前,数据库里的时间都需要更新到与新地点保持一致。

还会有更为重要的问题。不同时区的用户将会很难知道什么时候发送邮件,如果用户看到的是PST时区的时间,他们就很难知道邮件是什么时候发送的,这就需要用户根据这个时间做相应的调整。

很显然这不是一个好的选择,这也是我们为什么在创建数据库时就使用UTC时区保存时间戳。

在标准化时间戳为UTC时,解决了移动服务器的问题。但是他不能解决第二个问题,数据和时间在世界上不同地方使用UTC展现给用户。

假设一个用户在PST时区下午3点发送了一封邮件,这封邮件立刻显示在他面前,上面写着11:00pm,或者更具体点(23:00)。

我写这个文章的目的也就是让我们的用户不再因为数据和时间的显示而困惑。

使用具体的时间戳

通常的解决方法是,每一个用户都从UTC转化到当地的时间。这就需要我们动态变化,从而使数据库的UTC与之保持一致。

但是我们怎么知道用户在哪呢?

许多网站都有一个设置页面设置他们的时区。这就需要我们添加一个新的页面,并在表单上提供下拉框让用户选择时区,用户第一次登录的时候需要设置时区,并把它作为注册的一部分。

这是一个正常的解决方法,但是这对于用户来说有点累赘,用户需要输入一条他们已经在操作系统中配置过的信息。所以如果我们能抓取到用户电脑里设置的时区那解决问题会变得更有效率。

出于安全因素,浏览器不允许我们进入用户操作系统获取信息。即使它允许,我们也得知道在Windows,Linux,Mac,iOS,Android中从哪儿能获得到时区,这还不包括其他非主流操作系统。

在浏览器中得到用户的时区,然后通过标准的Javascript API获取到。在Web 2.0世界中用户允许Javascript执行(很少有网站不使用Javascript),所以通过Javascript获取用户时区是可行的。

我们用Javascript有两种方式配置可用的时区:

    老派的做法:当用户第一次登录服务器时让浏览器以某种方式发送时区信息给我们。这个可以通过Ajax调用,或者更简单的通过meta refresh tag来实现。一旦服务器知道了时区信息,它就能保存它在用户session中,然后调整所有页面的时间显示。
    新派的做法:不改变服务器端的任何东西,但仍然会发送UTC时间戳到客户端浏览器。转换UTC到本地时间的工作通过Javascript在客户端执行。

两种方法都是有效的,但第二种更有优势一点。浏览器能依照系统本地配置最好滴完成时间转换。像上午/下午 vs 24小时制,日/月/年 vs 月/日/年 还有其他各种文化的格式,这些格式都是浏览器可访问的,但服务器就不一定了。

如果这些还不够,那新派的做法还有一个更大的优势,而且别人已经为我们做了这件事(moment.js要登场了)!

moment.js简介

Moment.js 是一个小、免费、开源的Javascript库,它将日期和事件提升到另一个等级。它提供了能想象到的所有的时间日期格式,下面就是一些。

要在我们的应用中使用moment.js就需要在我们的模板文件中写那么一丢丢的Javascript代码。我们先来通过ISO 8601 时间来创建一个moment对象。例如:通过上面Python例子的UTC时间来创建一个moment对象,就像这样:
 

moment("2012-12-31T23:55:13 Z")

一旦对象被创建,它就可以被转化成各种各样格式类型的string。例如,将一个灰常冗长的时间显示转换为本地系统的时间:
 

moment("2012-12-31T23:55:13 Z").format('LLLL');

下面就是转换以后的时间显示:

Tuesday, January 1 2013 7:55 AM

这儿有更多的例子将同样的时间戳转化为不同的格式:

在Python的Flask框架中使用日期和时间的教程

这个类库对转化选项的支持不止这些。除了format()之外,它还提供了fromNow()和calendar()这些更友好的时间戳转化方法: 

在Python的Flask框架中使用日期和时间的教程

 注意上面所有的例子中服务器转换相同的UTC时间,而你自己的本地浏览器则会转换不同的时间。

最后我们补上漏掉的一点Javascript小技巧,在页面中显而易见的是,代码实际上由moment返回了string类型。最简单的完成方式是用Javascript的document.write方法:
 

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

通过使用Javascript的document.write是灰常简单和直接的方式来生成一部分HTML代码,然而需要注意的是这种方式有一些限制。最需要主义的一点就是document.write方法只能在document被加载时使用,当document加载完成后,它便不能修改document了。这个限制的结果就是当通过 Ajax 来加载数据时这种解决方案就失效了。
 
整合moment.js

这儿我们需要做一点点事把moment.js添加到我们的微博客中.

首先,我们需要下载moment.min.js这个库到/app/static/js这个文件夹中,这样它就可以作为静态文件为客户端服务。

然后我们在我们的模板文件(fileapp/templates/base.html)中添加这个库(moment.min.js)的引用:

 

<script src="/static/js/moment.min.js"></script>

现在我们可以在模板文件中添加<script>标签来显示我们想要的时间戳。但我们想替换这种方法,于是我们给moment.min.js加了一层包装来更好的从模板中调用它。这样将节约我们将来修改时间戳代码的时间,因为这样我们只要在一个地方修改它就好了。

我们的包装是一个灰常简单的Python类(fileapp/momentjs.py):
 

from jinja2 import Markup
 
class momentjs:
 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方法没有直接返回一个string类型而是返回一个由我们的模板引擎Jinja2提供的Markup对象。这么做的原因是Jinja2默认会转义所有string,举例来说,我们的<script>标签不能被传到客户端,而会被替换成<script>("<"">"这些符号被转义了,译者注)。所以,用Markup对象来包装这些string会告诉Jinja2这些string不能被转义。

现在我们有了一个包装器,我们需要通过Jinja2来连接,这样我们的模板就可以来调用它(fileapp/__init__.py):
 

from momentjs import momentjs
app.jinja_env.globals['momentjs'] = momentjs

这样Jinja2就会把我们的类当做全局变量来暴露给所有的模板。

现在我们准备开始修改我们的模板。在我们的应用中有两个地方来显示日期和事件。一个是在用户属性页面,我们显示”最后看过“的时间。对于这个时间戳我们将用calendar()方法来转换(fileapp/templates/user.html):
 

{% if user.last_seen %}
<p><em>Last seen: {{momentjs(user.last_seen).calendar()}}</em></p>
{% endif %}

第二个地方是post的子模板中,它被index,user和search页面调用。在这儿我们用fromNow()方法来转换,因为post是多久前被创建的比它具体是哪一个时刻被创建的重要。要提取转换以后的post时间到子页面,我们只需要改动一个地方就可以影响所有需要转换的post时间(fileapp/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>

随着这些模板的改动,我们便解决了所有的时间戳问题。我们不需要单独的去修改服务器端的代码!
 
结束语

不知不觉中,今天我们通过做一些客户端时间日期配置的改变,已经朝使微博客变得更受多国用户青睐迈出重要一步。

在这个系列的下一部分中,我们将要使微博客支持多国语言(国际化),从而让它不同国家用户的更受欢迎。

同时,这儿提供了集成moment.js的应用得下载链接:

下载 microblog-0.13.zip。

Python 相关文章推荐
使用Python脚本操作MongoDB的教程
Apr 16 Python
Python实现字符串格式化的方法小结
Feb 20 Python
Python给你的头像加上圣诞帽
Jan 04 Python
对Python模块导入时全局变量__all__的作用详解
Jan 11 Python
由Python编写的MySQL管理工具代码实例
Apr 09 Python
python redis连接 有序集合去重的代码
Aug 04 Python
Pyinstaller 打包exe教程及问题解决
Aug 16 Python
python模块导入的方法
Oct 24 Python
python pygame实现球球大作战
Nov 25 Python
Spring Cloud Feign高级应用实例详解
Dec 10 Python
python 删除系统中的文件(按时间,大小,扩展名)
Nov 19 Python
python三子棋游戏
May 04 Python
在Python的Flask框架下收发电子邮件的教程
Apr 21 #Python
在Python的Flask框架中实现全文搜索功能
Apr 20 #Python
Python的Flask框架中实现分页功能的教程
Apr 20 #Python
在Python的Flask框架中实现单元测试的教程
Apr 20 #Python
Python的Flask框架中实现登录用户的个人资料和头像的教程
Apr 20 #Python
Python的Flask框架中实现简单的登录功能的教程
Apr 20 #Python
Python的Flask框架与数据库连接的教程
Apr 20 #Python
You might like
MySQL数据源表结构图示
2008/06/05 PHP
DedeCMS 核心类TypeLink.class.php摘要笔记
2010/04/07 PHP
探讨php中防止SQL注入最好的方法是什么
2013/06/10 PHP
浅谈PHP中pack、unpack的详细用法
2018/03/12 PHP
php 截取中英文混合字符串的方法
2018/05/31 PHP
js检测客户端不是firefox则提示下载
2007/04/07 Javascript
jquery复选框CHECKBOX全选、反选
2008/08/30 Javascript
jquery简单的拖动效果实现原理及示例
2013/07/26 Javascript
JavaScript中的Math.sin()方法使用详解
2015/06/15 Javascript
跟我学习javascript的函数调用和构造函数调用
2015/11/16 Javascript
深入理解关于javascript中apply()和call()方法的区别
2016/04/12 Javascript
Spring MVC中Ajax实现二级联动的简单实例
2016/07/06 Javascript
微信小程序 省市区选择器实例详解(附源码下载)
2017/01/05 Javascript
通过jsonp获取json数据实现AJAX跨域请求
2017/01/22 Javascript
解决vue-router路由拦截造成死循环问题
2020/08/05 Javascript
vue项目中js-cookie的使用存储token操作
2020/11/13 Javascript
python中enumerate函数遍历元素用法分析
2016/03/11 Python
python3使用PyMysql连接mysql数据库实例
2017/02/07 Python
Sanic框架路由用法实例分析
2018/07/16 Python
python实现简单多人聊天室
2018/12/11 Python
python利用小波分析进行特征提取的实例
2019/01/09 Python
Python单元测试模块doctest的具体使用
2020/02/10 Python
基于打开pycharm有带图片md文件卡死问题的解决
2020/04/24 Python
如何理解Python中的变量
2020/06/01 Python
Python Opencv实现单目标检测的示例代码
2020/09/08 Python
OPPO手机官方商城:中国手机市场出货量第一品牌
2017/10/18 全球购物
法国在线药房:DoctiPharma
2020/10/21 全球购物
电子商务专业毕业生工作推荐信
2013/11/17 职场文书
大三毕业自我鉴定
2014/01/15 职场文书
业务员简历自我评价
2014/03/06 职场文书
工作所在部门证明
2014/09/21 职场文书
校园广播稿100字
2014/10/06 职场文书
人事专员岗位职责
2015/02/03 职场文书
房屋产权证明书
2015/06/19 职场文书
详解JVM系列之内存模型
2021/06/10 Javascript
Alexa停服!网站排名将何去何从?目前还没有替代品。
2022/04/15 杂记