📜 ⬆️ ⬇️

Flask Mega-Tutorial, Part 13: Date and Time

This is the thirteenth article in the series, where I describe my experience of writing a Python web application using the Flask mic framework.

The purpose of this guide is to develop a fairly functional microblog application, which I decided to call microblog, in the absence of originality.



GitHub note

For those who have not noticed, I recently transferred the source codes of microblog to github. The repository is located at the following address:
https://github.com/miguelgrinberg/microblog
For each step of this tutorial, appropriate tags have been added.

')

Problem with timestamp


One feature of our application that we have ignored for a long time is the display of date and time.

Until now, we trusted python to display the timestamps stored in our User and Post objects in its sole discretion, which is not really a good solution.

Consider the following example. I write this at 15:54, December 31, 2012. My time zone is PST (or UTC -8 if you prefer). By running the python interpreter, I get the following results:

>>> 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 

A call now () returns the correct time for my time zone, while utcnow () displays GMT time.

So which function is better to use?

If we decide to use now (), then all the timestamps that we plan to save to our database will depend on the location of our server and this may cause some problems.

One day it may be necessary to transfer the server to another time zone. And in this case, all time stamps stored in the database will need to be corrected in accordance with the new local time before starting the server.

But there is a more serious problem with this approach. For users from different time zones, it will be difficult to determine exactly when the post was published, if they see it in the PST time zone. To be defined, users should know also that time is specified concerning PST.

Obviously, this is not the best option, and that is why when we created our database, it was decided to store the time stamps in GMT.

However, having brought timestamps to a certain standard, we solved only the first problem (with transferring the server to a different time zone), while the second question remained without a solution - timestamps will be displayed to absolutely all Greenwich users.

This can still confuse users. Imagine a user from the PST time zone who posted a blog entry around 3:00 pm. The record immediately appeared on the main page, but it indicated the creation time of 23:00.

Thus, the purpose of today's article is to clarify the issues of displaying the date and time, so as not to confuse our users.

User timestamps


An obvious solution to the problem is an individual time conversion according to GMT to local time for each user. This will allow us to continue using Greenwich Mean Time in our database, and the time converted for each user on the fly will make the time consistent for everyone.

But how do we know the location of our users?

Many sites have a settings page where the user can specify their time zone. To do this, you need to add a new page with a form containing a drop-down list of existing time zones. Part of the registration will be the answer to the question about the user's time zone.
Despite the fact that this solution is fully functional and solves the task, it seems somewhat redundant - asking the user to enter information that has already been configured and stored in the OS. It seems to be much more efficient, just get the time zone settings from the user's computer.

However, for security reasons, browsers will not allow us to access the user's system and, therefore, to obtain the necessary information. Even if it were technically possible, we would need to know exactly where to look for the current settings for time zones on Windows, Linux, Mac, iOS, and Android (and this is not counting the less common operating systems).

It turns out that the browser knows the user's time zone and makes it available through the standard javascript interface. In the modern Web 2.0 world, it’s quite possible to count on javascript enabled in the browser (in practice, no modern website will work correctly with javascript disabled), so this solution is worth considering.

We have two options for using the javascript time zone information:
  1. The traditional (oldschool) approach is to ask the browser to send us information about the user's time zone when the user first visits our site. This can be done with Ajax or, much more simply, with a meta refresh tag. As soon as the server recognizes the time zone, it can save this information in the user's session and adjust all the time stamps in the template during its drawing.
  2. The modern (new-school) approach is to not involve the server in this process and allow it to send timestamps to the browser of the user in GMT. And the conversion of time in accordance with the user's time zone will occur on the client using javascript.


Both options are correct, but the second has an advantage. The browser has more features in the correct date display in accordance with the settings of the user's system locale. Details such as AM / PM (12-hour) or 24-hour time format are used, DD / MM / YY or MM / DD / YY date format is preferred and many others are available to the browser and are unknown to the server.

And if these arguments in favor of the second option are not enough, then there is another advantage of this approach. It turns out that all the work has already been done for us!

Meet moment.js


Moment.js is a compact open source javascript library that takes date and time drawing to a new level. It provides all sorts of formatting options, and one more thing.

To use moment.js in our application, we will have to add quite a bit of javascript to our templates. We start by creating the moment object from time in ISO 8601 format. For example, using GMT from the example above, we will create a moment object like this:

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

Once an object is created, it can be converted to a string with a huge variety of formats. For example, a rather verbose display in accordance with the system settings of the locale might look like this:

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

And here is how the system will display this date:

 Tuesday, January 1 2013 1:55 AM 

Here are some more examples of the same timestamp displayed in various formats:
FormatResult
L01/01/2013
LlJanuary 1 2013
LllJanuary 1 2013 1:55 AM
LlllTuesday, January 1 2013 1:55 AM
ddddTuesday

Support by the library of various options does not end there. In addition to format (), the library offers fromNow () and calendar (), with a much more friendly display of timestamps:

FormatResult
fromNow ()2 years ago
calendar ()01/01/2013

Note that in all the examples presented, the server gives the same GMT time, and the necessary conversions are made by the user's browser.

And the last part of the javascript magic we missed is to make the string returned by the method of the moment object visible on the page. The easiest way to achieve this is to use the javascript document.write function, as shown below:

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

And although the use of document.write is very simple and straightforward as a way to generate a fragment of an HTML document using javascript, you should remember that this implementation has some limitations. The most significant of these is that document.write can only be used during document loading and cannot be used to change a document after it has finished loading. And, as a consequence of this limitation, this solution will not work when loading data via Ajax.

Integration moment.js


There are a few things we need to do to use moment.js in our application.

First, you need to place the downloaded library moment.min.js in the folder / app / static / js, so that it will be given to clients as static (static file).

Then, add this library to our base template (file app / templates / base.html):
 <script src="/static/js/moment.min.js"></script> 

Now we can add <script> tags to the templates that display the time stamps and the job will be done. But instead, we will create a wrapper for moment.js, which we can use in templates. This will save us time in the future if we decide to change the code for displaying timestamps, because it will be enough to change just one file.

Our wrapper will be a very simple python class (file 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:%SZ"), 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()") 


Notice that the render method does not return a string directly; instead, it is an instance of the Markup class provided by Jinja2 by our template engine. The point is that Jinja2 by default escapes all lines, so <script> can get to the client in this form & lt; script & gt ;. By returning an instance of the Markup object instead of a string, we told Jinja2 that this line was not necessary to escape.

Now that we have a wrapper, we need to add it to Jinja2 so that we can use it in our templates (file app / __ init__.py):

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

By this we allowed Jinja2 to provide our class as a global variable in all templates.

Now we are ready to make changes to our templates. There are two places in our application where we display the date and time. The first is the user profile page, where we show the time of the last visit. To display on this page, we will apply the calendar () formatting (file app / templates / user.html):

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

The second place is a post template that is used on the index, user and search pages. In the post template, we apply the formatting fromNow (), since the exact time of post creation is not as important as the elapsed time after post creation. Since we selected post rendering to a separate template, now we only need to make changes in one place to change all pages displaying posts (file 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> 

And with this simple change, we solved all our problems with timestamps. And, at the same time, no changes were required to the server code!

Conclusion


Without even noticing, today we have taken a significant step towards increasing the availability of our application for users from all over the Network, by changing the date display in accordance with the user's locale settings.

In the next article of this series, we will try to please our foreign users even more, since we will include support for several languages.

Download: microblog-0.13.zip.
Or, if you like it better, you can find the source code on GitHub .

Miguel

Source: https://habr.com/ru/post/236753/


All Articles