在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的Scrapy框架编写web爬虫的简单示例
Apr 17 Python
Python常用小技巧总结
Jun 01 Python
Python如何为图片添加水印
Nov 25 Python
Python简单实现阿拉伯数字和罗马数字的互相转换功能示例
Apr 17 Python
python3+PyQt5实现自定义窗口部件Counters
Apr 20 Python
Python/ArcPy遍历指定目录中的MDB文件方法
Oct 27 Python
python中数组和矩阵乘法及使用总结(推荐)
May 18 Python
python实现自动化报表功能(Oracle/plsql/Excel/多线程)
Dec 02 Python
Tensorflow的常用矩阵生成方式
Jan 04 Python
Python判断字符串是否为空和null方法实例
Apr 26 Python
基于python模拟TCP3次握手连接及发送数据
Nov 06 Python
Django和Ueditor自定义存储上传文件的文件名
Feb 25 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
实现树状结构的两种方法
2006/10/09 PHP
判断是否为指定长度内字符串的php函数
2010/02/16 PHP
奇怪的PHP引用效率问题分析
2012/03/23 PHP
php的POSIX 函数以及进程测试的深入分析
2013/06/03 PHP
php自定义函数br2nl实现将html中br换行符转换为文本输入中换行符的方法【与函数nl2br功能相反】
2017/02/17 PHP
PHP上传图片到数据库并显示的实例代码
2019/12/20 PHP
用JavaScript仿PS里的羽化效果代码
2011/12/20 Javascript
JavaScript中按位“异或”运算符使用介绍
2014/03/14 Javascript
js中数组排序sort方法的原理分析
2014/11/20 Javascript
Node.js中的流(Stream)介绍
2015/03/30 Javascript
BootStrap中关于Select下拉框选择触发事件及扩展
2016/11/22 Javascript
JavaScript中校验银行卡号的实现代码
2016/12/19 Javascript
详解webpack编译多页面vue项目的配置问题
2017/12/11 Javascript
javascript实现数字配对游戏的实例讲解
2017/12/14 Javascript
jQuery获取所有父级元素及同级元素及子元素的方法(推荐)
2018/01/21 jQuery
vue的keep-alive中使用EventBus的方法
2019/04/23 Javascript
Electron + vue 打包桌面操作流程详解
2019/06/24 Javascript
JavaScript深入V8引擎以及编写优化代码的5个技巧
2019/06/24 Javascript
微信小程序表单验证插件WxValidate的二次封装功能(终极版)
2019/09/03 Javascript
vuex实现像调用模板方法一样调用Mutations方法
2019/11/06 Javascript
ES6 Object.assign()的用法及其使用
2020/01/18 Javascript
基于vue+echarts数据可视化大屏展示的实现
2020/12/25 Vue.js
[34:39]Secret vs VG 2018国际邀请赛淘汰赛BO3 第二场 8.23
2018/08/24 DOTA
python使用正则表达式提取网页URL的方法
2015/05/26 Python
python3.6+django2.0开发一套学员管理系统
2018/03/03 Python
python数据处理之如何选取csv文件中某几行的数据
2019/09/02 Python
浅谈Python3识别判断图片主要颜色并和颜色库进行对比的方法
2019/10/25 Python
Python生态圈图像格式转换问题(推荐)
2019/12/02 Python
浅谈Python中re.match()和re.search()的使用及区别
2020/04/14 Python
导致python中import错误的原因是什么
2020/07/01 Python
Python使用OpenPyXL处理Excel表格
2020/07/02 Python
详解使用canvas保存网页为pdf文件支持跨域
2018/11/23 HTML / CSS
泰国综合购物网站:Lazada泰国
2018/04/09 全球购物
软件项目实施计划书
2014/05/02 职场文书
12.4法制宣传日标语
2014/10/08 职场文书
解决IDEA翻译插件Translation报错更新TTK失败不能使用
2022/04/24 Python