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

Оглавление

Один из аспектов моего приложения для ведения микроблогов, который я долгое время игнорировал, - это отображение дат и времени. До сих пор я просто позволял Python отображать объект datetime в модели User и даже не потрудился отобразить его в модели Post. В этой главе вы узнаете, как работать с этими временными метками.

Ссылки на GitHub для этой главы: BrowseZipDiff.

Адский часовой пояс

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

Совершенно ясно, что сервер должен управлять временем, которое является согласованным и независимым от его собственного местоположения и местоположения пользователей. Если это приложение разрастется до такой степени, что потребуется несколько производственных серверов в разных регионах мира, я бы не хотел, чтобы каждый сервер записывал временные метки в базу данных в разных часовых поясах, потому что это сделало бы невозможной работу с этими временами. Поскольку UTC является наиболее используемым единым часовым поясом и поддерживается в классе datetime, именно его я и собираюсь использовать.

В главе 4 вы видели, как создавать временные метки UTC для записей в блоге. В качестве напоминания, вот краткий пример, показывающий, как это было сделано.:

>>> from datetime import datetime, timezone
>>> str(datetime.now(timezone.utc))
'2023-11-19 19:05:51.288261+00:00'

Но с этим подходом связана важная проблема. Пользователям из разных мест будет ужасно сложно определить, когда была сделана публикация, если они будут видеть время в часовом поясе UTC. Им нужно было бы заранее знать, что время указано в UTC, чтобы они могли мысленно подогнать его к своему собственному часовому поясу. Представьте пользователя, скажем, в часовом поясе PDT на Западном побережье США, который публикует что-то в 15: 00 и сразу видит, что сообщение появляется в 10: 00 по времени UTC, или, если быть более точным, в 22: 00. Это будет очень запутанно.

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

Преобразование часовых поясов

Очевидным решением проблемы является преобразование всех временных меток из сохраненных единиц UTC в местное время каждого пользователя при их отображении. Это позволяет серверу продолжать использовать UTC для обеспечения согласованности, в то время как преобразование "на лету", адаптированное к каждому пользователю, решает проблему удобства использования. Сложная часть этого решения - знать местоположение каждого пользователя.

На многих веб-сайтах есть страница конфигурации, где пользователи могут указывать свой часовой пояс. Для этого мне потребуется добавить новую страницу с формой, в которой я представляю пользователям раскрывающийся список часовых поясов. При первом входе на сайт пользователей могут попросить ввести их часовой пояс в рамках регистрации.

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

Как выясняется, веб-браузер знает часовой пояс пользователя и предоставляет его через стандартные API JavaScript даты и времени. На самом деле есть два способа воспользоваться информацией о часовом поясе, доступной через JavaScript:

  • Подход "старой школы" заключался бы в том, чтобы веб-браузер каким-то образом отправлял информацию о часовом поясе на сервер, когда пользователь впервые входит в приложение. Это можно было бы сделать с помощью вызова Ajax или гораздо проще с помощью мета-тега обновления. Как только сервер узнает часовой пояс, он может сохранить его в сеансе пользователя или записать в таблицу users в базе данных, и с этого момента корректировать с его помощью все временные метки во время отображения шаблонов.

  • Подход "новой школы" заключается в том, чтобы ничего не менять на сервере и позволить преобразованию UTC в местный часовой пояс происходить в браузере с использованием JavaScript.

Оба варианта допустимы, но второй имеет большое преимущество. Знания часового пояса пользователя не всегда достаточно для представления дат и времени в формате, ожидаемом пользователем. Браузер также имеет доступ к конфигурации языкового стандарта системы, которая определяет такие параметры, как время утра / вечера в сравнении с 24-часовыми часами, формат отображения даты DD / MM / ГГГГ в сравнении с MM / DD / ГГГГ и многие другие культурные или региональные стили.

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

Знакомство Moment.js и Flask-Moment

Moment.js это небольшая библиотека JavaScript с открытым исходным кодом, которая выводит отображение даты и времени на новый уровень, поскольку предоставляет все мыслимые варианты форматирования, а затем и некоторые другие. Некоторое время назад я создал Flask-Moment, небольшое расширение Flask, которое позволяет очень легко интегрировать moment.js в ваше приложение.

Итак, давайте начнем с установки Flask-Moment:

(venv) $ pip install flask-moment

Это расширение добавляется в приложение Flask обычным способом:

app/__init__.py: Пример Flask-Moment.

# ...
from flask_moment import Moment

app = Flask(__name__)
# ...
moment = Moment(app)
# ...

В отличие от других расширений, Flask-Moment работает вместе с moment.js, поэтому все шаблоны приложения должны включать эту библиотеку. Чтобы гарантировать, что эта библиотека всегда доступна, я собираюсь добавить ее в базовый шаблон. Это можно сделать двумя способами. Самый прямой способ - явно добавить тег <script>, который импортирует библиотеку, но Flask-Moment упрощает задачу, предоставляя функцию moment.include_moment(), которая генерирует тег <script>:

app/templates/base.html: Включить moment.js в базовый шаблон.

...

    {{ moment.include_moment() }}
  </body>
</html>

В большинстве случаев библиотеки JavaScript, используемые приложением, включены в конец содержимого <body>, где находится загрузочный JavaScript-код.

Использование Moment.js

Moment.js делает класс moment доступным для браузера. Первым шагом для отображения временной метки является создание объекта этого класса, передающего желаемую временную метку в формате ISO 8601. Вот пример, запущенный в консоли JavaScript браузера:

t = moment('2021-06-28T21:45:23+00:00')

Если вы не знакомы со стандартным форматом даты и времени ISO 8601, этот формат выглядит следующим образом:

{yyyy}-{mm}-{dd}T{hh}:{mm}:{ss}{tz}

Я уже решил, что буду работать только с часовыми поясами UTC, поэтому последней частью всегда будет +00:00 или в некоторых случаях эквивалент Z, который представляет UTC в стандарте ISO 8601.

В объекте moment предусмотрено несколько методов для различных вариантов рендеринга. Ниже приведены некоторые из наиболее распространенных вариантов:

moment('2021-06-28T21:45:23+00:00').format('L')
'06/28/2021'
moment('2021-06-28T21:45:23+00:00').format('LL')
'June 28, 2021'
moment('2021-06-28T21:45:23+00:00').format('LLL')
'June 28, 2021 10:45 PM'
moment('2021-06-28T21:45:23+00:00').format('LLLL')
'Monday, June 28, 2021 10:45 PM'
moment('2021-06-28T21:45:23+00:00').format('dddd')
'Monday'
moment('2021-06-28T21:45:23+00:00').fromNow()
'2 years ago'

В этом примере создается объект moment, инициализированный 28 июня 2021 года в 21:45 по Гринвичу. Вы можете видеть, что все параметры, которые я пробовал выше, отображаются в UTC + 1, который является часовым поясом, настроенным на моем компьютере. Вы можете ввести вышеуказанные команды в консоли вашего браузера, убедившись, что в странице, на которой вы открываете консоль, включена moment.js. Вы можете сделать это в микроблоге, при условии, что вы внесли вышеуказанные изменения для включения moment.js, или также на https://momentjs.com/.

Обратите внимание, как разные методы создают разные представления. С помощью метода format() вы управляете форматом выходных данных с помощью строки формата. Метод fromNow() интересен тем, что он отображает временную метку по отношению к текущему времени, поэтому вы получаете выходные данные, такие как "минуту назад" или "через два часа" и т.д.

Если вы работали непосредственно в JavaScript, приведенные выше вызовы возвращают строку с отображаемой временной меткой. Затем вам предстоит вставить этот текст в нужное место на странице, что, к сожалению, требует работы с DOM. Расширение Flask-Moment значительно упрощает использование moment.js за счет включения в ваши шаблоны объекта moment, аналогичного объекту JavaScript.

Давайте посмотрим на временную метку, которая отображается на странице профиля. Текущий шаблон user.html позволяет Python генерировать строковое представление времени. Теперь я могу отобразить эту временную метку с помощью Flask-Moment следующим образом:

app/templates/user.html: Отрисовка временной метки с помощью moment.js.

{% if user.last_seen %}
<p>Last seen on: {{ moment(user.last_seen).format('LLL') }}</p>
{% endif %}

Итак, как вы можете видеть, Flask-Moment использует синтаксис, аналогичный синтаксису библиотеки JavaScript, с одним отличием, заключающимся в том, что аргументом для moment() теперь является объект Python datetime, а не строка ISO 8601. Вызов moment(), выполняемый из шаблона, автоматически генерирует необходимый код JavaScript для вставки отображаемой временной метки в нужное место DOM.

Второе место, где я могу воспользоваться преимуществами Flask-Moment, находится во вложенном шаблоне _post.html, который вызывается с главной страницы и страницы пользователя. В текущей версии шаблона каждому сообщению предшествует строка "username says:". Теперь я могу добавить временную метку, отображаемую с помощью fromNow():

app/templates/_post.html: Отрисовка временной метки во вложенном шаблоне post.

<a href="{{ url_for('user', username=post.author.username) }}">
    {{ post.author.username }}
</a>
said {{ moment(post.timestamp).fromNow() }}:
<br>
{{ post.body }}

Ниже вы можете увидеть, как выглядят обе эти временные метки при рендеринге с помощью Flask-Moment и moment.js:

Комментарии (0)