Django - Возвращает месяцы, которые охватывают сообщения пользователя с заданным временным смещением от UTC

Я работаю над приложением для социальных сетей и хочу, чтобы пользователь A мог получить все месяцы, которые опубликовал пользователь B в часовом поясе пользователя A. Внешний интерфейс выполнен на Javascript, и наиболее разумным было бы, если бы пользователь отправлял смещение своего часового пояса от UTC (это можно сделать с помощью new Date().getTimezoneOffset() в JS), поскольку именно так в Django сохраняются даты часовых поясов. Так, например, пользователь A будет пинговать url, который выглядит примерно как /<user B's username>/<year>/<user A's time zone offset from UTC>.

Допустим, пользователь B находится в PST и создал сообщение 15 ноября 2021 года в 12:00 вечера и 31 декабря 2021 года в 11:00 вечера. Тогда, допустим, пользователь A, который находится в EST, вызывает этот url, тогда он должен вернуть [November, January] (Или эквивалентное числовое отображение), потому что 31 декабря в 11:00pm PST - это 1 января в 2:00am EST.

Как вернуть все месяцы, которые пользователь B опубликовал в часовом поясе пользователя A, задав смещение часового пояса пользователя A относительно UTC?

models.py

class Post(models.Model):
    uuid = models.UUIDField(primary_key=True)
    created = models.DateTimeField('Created at', auto_now_add=True)
    updated_at = models.DateTimeField('Last updated at', auto_now=True, blank=True, null=True)
    creator = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="post_creator")
    body = models.CharField(max_length=POST_MAX_LEN, validators=[MinLengthValidator(POST_MIN_LEN)])

Нет возможности написать запрос для отбора объектов с определенным часовым поясом. Поэтому, если вы хотите сделать такой запрос, вы либо запрашиваете все посты за этот год, а затем фильтруете набор запросов с помощью чистых методов python (например, filter()); либо вы добавляете новое поле в пост, где вы сохраняете часовой пояс, чтобы вы могли написать свой запрос. Это было бы более эффективным решением

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

Если вы включите поддержку часовых поясов в Django (см. Документацию по часовым поясам в Django), вы сможете делать запросы, используя объекты datetime с учетом часовых поясов.

Чтобы включить его, необходимо установить USE_TZ = True в файле настроек.

Вам необходимо вызвать метод timezone.activate , чтобы установить часовой пояс текущего пользователя.

Допустим, вы собираетесь передать смещение UTC в URL, как указано выше (/<user B username>/<new Date().getTimezoneOffset() in JS>), и это смещение будет доступно в переменной utc_offset. Затем вы можете установить "текущий часовой пояс" (т.е. часовой пояс текущего пользователя) следующим образом:

import datetime
from django.utils import timezone

timezone.activate(datetime.timezone(datetime.timedelta(hours=utc_offset)))

Теперь при выполнении timezone.now() дата будет возвращена в часовом поясе пользователя.

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

Если вы используете Django Rest Framework (DRF), DateTimeFields также будет возвращаться в часовом поясе пользователя.

Если вы не используете DRF или не выводите даты в шаблонах, вы можете получить значение времени в часовом поясе пользователя, используя astimezone и timezone.get_current_timezone():

post.created.astimezone(timezone.get_current_timezone())

Похоже, что вам нужно сделать хранение данных пользователя через datetime(). Лучший способ, который я нашел для решения той же проблемы - хранить временные зоны каждого пользователя и отбрасывать все дубликаты. Затем, когда любой пользователь публикует данные, он записывает дату и время каждого часового пояса в этот момент и сохраняет их вместе с сообщением. Затем при отображении данных вы определяете, в каком часовом поясе находится пользователь, и отображаете дату и время конкретного часового пояса в отображаемом сообщении.

Прежде всего, вы должны решить проблемы с часовым поясом пользователей B и часовым поясом сервера! Как вы сказали,

Допустим, пользователь B находится в PST и создал сообщение в... и 31 декабря 2021 года в 11:00 вечера.

это зависит от часового пояса вашего сервера, как это будет записано в вашей базе данных! Если часовой пояс вашего сервера UTC, то у вас будет запись: 2021-01-01 07:00. Какая дата была на самом деле, а? Хорошо, предположим, что у вас часовой пояс сервера UTC, и в БД будет запись, соответствующая 2021-01-01 07:00. Итак, все, что вам нужно в этом случае, это добавить смещение к datetime и получить месяц, вот так:

def get_month_from_user_side(dt: datetime, offset: int) -> str:
    result_dt = dt - timedelta(minutes=offset)
    return result_dt.strftime('%B, %Y')

Я протестировал его:

from datetime import date, datetime, timedelta

if __name__ == '__main__':
    user_b_post_dt = datetime(2022, 1, 1, 7, 00) # This time – from DB
    offset = -120 # This should be got from JS from user A
    print(
        get_month_from_user_side(user_b_post_dt, offset)
    )

>>January, 2022

"Магия" здесь в том, что если пользователь B попросит свои собственные посты, он получит декабрьские, как и должно быть!

if __name__ == '__main__':
    user_b_post_dt = datetime(2022, 1, 1, 7, 00) # This time – from DB
    offset = 480 # PST time offset from UTC (for user B)
    print(
        get_month_from_user_side(user_b_post_dt, offset)
    )

>>December, 2021

Примечание: Этот код нуждается в тестах для определения знака +- timedelta, может быть ошибка.

В руководстве Django рекомендуется использовать set USE_TZ=True https://docs.djangoproject.com/en/4.0/topics/i18n/timezones/#setup, что я и делаю. Я также рекомендую установить TIME_ZONE = 'UTC'

Вернуться на верх