[Перевод] Мега-Учебник Flask, Часть 13: Дата и время

Это тринадцатая статья в серии, где я описываю свой опыт написания веб-приложения на Python с использованием микрофреймворка Flask.Цель данного руководства — разработать довольно функциональное приложение-микроблог, которое я за полным отсутствием оригинальности решил назвать microblog.

Оглавление

Примечание касательно GitHub Для тех кто не заметил, я не так давно перенес исходные коды microblog-а на github. Репозитарий расположен по следующему адресу: https://github.com/miguelgrinberg/microblogДля каждого шага этого руководства добавлены соответствующие тэги.

Проблема с timestamp Одна особенность нашего приложения, которую мы игнорировали длительное время, это отображение даты и времени.До настоящего момента, мы доверяли python отображать метки времени, хранящиеся в наших объектах User и Post по собственному усмотрению, что на самом деле не является хорошим решением.

Рассмотрим следующий пример. Я пишу это в 15:54, 31 декабря 2012 года. Мой часовой пояс 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 () возвращает правильное время для моего часового пояса, в то время как utcnow () отображает время по Гринвичу.Так какую функцию лучше использовать? Если мы решим использовать now (), тогда все временные метки, которые мы планируем сохранять в нашу базу данных, будут зависеть от местоположения нашего сервера и это может породить некоторые проблемы.Однажды может возникнуть необходимость перенести сервер в другой часовой пояс. И в этом случае все хранящиеся в базе данных временные метки нужно будет исправить в соответствии с новым локальным временем перед запуском сервера.

Но существует и более серьезная проблема, связанная с этим подходом. Для пользователей из разных часовых поясов будет сложно определить, когда именно был опубликован пост, если они будут видеть его в часовом поясе PST. Чтобы определиться, пользователи должны знать также что время указано относительно PST.

Очевидно, что это не самый оптимальный вариант, и именно поэтому когда мы создавали нашу базу данных, было принято решение хранить метки времени по Гринвичу.

Однако, приведя временные метки к некоторому стандарту, мы решили только первую проблему (с переносом сервера в другой часовой пояс), в то время как второй вопрос остался без решения — временные метки будут отображаться абсолютно всем пользователям по Гринвичу.

Это по-прежнему может запутать пользователей. Представьте себе пользователя из часового пояса PST, который разместил запись в блоге около 15:00. Запись тут же появилась на главной странице, но в ней указано время создания 23:00.

Таким образом, цель сегодняшней статьи — прояснить вопросы отображения даты и времени, чтобы не запутывать наших пользователей.

Временные метки пользователя Очевидным решением проблемы является индивидуальное преобразование времени по Гринвичу в локальное время для каждого пользователя. Это позволит нам продолжить использовать время по Гринвичу в нашей базе данных, а преобразованное на лету время для каждого пользователя, сделает время консистентным для всех.Но как мы узнаем местоположение наших пользователей?

На многих сайтах есть страница настроек, где пользователь может указать свой часовой пояс. Для этого потребуется добавить новую страницу с формой, содержащей выпадающий список существующих часовых поясов. Частью регистрации станет ответ на вопрос о часовом поясе пользователя.Несмотря на то, что данное решение является вполне работоспособным и решает поставленную задачу, оно представляется несколько избыточным — просить пользователя вводить информацию, которая уже однажды настроена и хранится у них в ОС. Кажется, было бы гораздо эффективнее, просто получить настройки часового пояса с компьютера пользователя.

Однако, из соображений безопасности, браузеры не позволят нам получить доступ к системе пользователя, а следовательно, и получить необходимую информацию. Даже если бы это было технически возможно, нам бы нужно было знать где именно искать текущие установки для часовых поясов на Windows, Linux, Mac, iOS, и Android (и это не считая менее распространенные ОС).

Оказывается, браузер знает часовой пояс пользователя и делает его доступным через стандартный javascript интерфейс. В современном Web 2.0 мире вполне можно рассчитывать на включенный в браузере javascript (на практике, ни один современный сайт не будет работать корректно с отключенным javascript), так что это решение достойно рассмотрения.

У нас есть два варианта использования информации о часовом поясе, предоставляемой javascript:

Традиционный (oldschool) подход — попросить браузер отправить нам информацию о часовом поясе пользователя, когда пользователь впервые заходит к нам на сайт. Это может быть реализовано при помощи Ajax или, что гораздо проще, при помощи тэга meta refresh. Как только сервер узнает часовой пояс он может сохранить эту информацию в сессии пользователя и откорректировать все временные метки в шаблоне во время его отрисовки. Современный (new-school) подход состоит в том, чтобы не привлекать к этому процессу сервер и позволить ему отправлять временные метки браузеру пользователя по Гринвичу. А преобразование времени в соответствии с часовым поясом пользователя будет происходить на клиенте при помощи javascript. Оба варианта корректны, но у второго есть преимущество. У браузера есть больше возможностей в правильной отрисовке даты в соответствии с настройками системной локали пользователя. Такие подробности, как AM/PM (12-часовой) или 24-часовой формат времени используется, ДД/MM/ГГ или MM/ДД/ГГ формат даты предпочтителен и многие другие доступны браузеру и неизвестны серверу.

И если этих доводов в пользу второго варианта недостаточно, то есть еще одно преимущество этого подхода. Оказывается, вся работа уже сделана за нас!

Знакомьтесь, moment.js Moment.js это компактная javascript библиотека с открытым исходным кодом, которая переводит отрисовку даты и времени на новый уровень. Она предоставляет всевозможные опции форматирования, и еще кое-что.Для использования moment.js в нашем приложении, нам придется добавить совсем немного javascript в наши шаблоны. Начнем мы с создания объекта  moment из времени в формате ISO 8601. К примеру, используя время по Гринвичу из примера выше, мы создадим объект moment вот так:

moment (»2012–12–31T23:55:13 Z») Как только объект создан, он может быть преобразован в строку с огромным разнообразием форматов. Например, довольно многословное отображение в соответствии с системными настройками локали, может выглядеть так: moment (»2012–12–31T23:55:13 Z»).format ('LLLL'); А вот как система отобразит эту дату: Tuesday, January 1 2013 1:55 AM Вот еще несколько примеров той же метки времени отображенной в различных форматах: Формат Результат L 01/01/2013 LL January 1 2013 LLL January 1 2013 1:55 AM LLLL Tuesday, January 1 2013 1:55 AM dddd Tuesday Поддержка библиотекой различных опций на этом не заканчивается. В дополнение к format () библиотека предлагает  fromNow () и calendar (), с гораздо более дружественным отображением временных меток: Формат Результат fromNow () 2 years ago calendar () 01/01/2013 Заметьте, что во всех представленных примерах, сервер отдает одно и то же время по Гринвичу, а необходимые преобразования производятся уже браузером пользователя.И последняя часть магии javascript, которую мы пропустили, это сделать строку возвращенную методом объекта moment видимой на странице. Самый простой способ добиться этого — использовать фунцию javascript document.write , как показано ниже:

document.write (moment (»2012–12–31T23:55:13 Z»).format ('LLLL')); И хотя использование document.write очень просто и понятно, как способ генерации фрагмента HTML документа при помощи javascript, следует помнить, что эта реализация имеет некоторые ограничения. Наиболее значительное из них состоит в том, что document.write может использоваться только во время загрузки документа и не может использоваться для изменения документа, после окончания его загрузки. И, как следствие этого ограничения, данное решение не будет работать при загрузке данных через Ajax.Интеграция moment.js Есть несколько вещей, которые нам нужно сделать, чтобы использовать  moment.js в нашем приложении.Первое, нужно поместить скачанную библиотеку moment.min.js в папку /app/static/js, так, что она будет отдаваться клиентам как статика (статичный файл).

Затем, следует добавить эту библиотеку в наш базовый шаблон (файлapp/templates/base.html):

Теперь мы можем добавить тэги  в шаблоны, которые отображают временные метки и дело будет сделано. Но вместо этого, мы создадим обертку для   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 (»» % (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 не возвращает непосредственно строку, вместо этого он экземпляр класса Markup, предоставляемого Jinja2, нашим шаблонизатором. Смысл в том, что Jinja2 по умолчанию экранирует все строки, так,