Django - При задании часового пояса, месяца и года получить все сообщения, созданные в этот день в этом часовом поясе

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

model.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)

Например, если пользователь создает 10 сообщений в ноябре, 2 в декабре 2021 года в PST. Тогда у меня есть представление, которое принимает month, year и time_zone и, допустим, url выглядит как /post/<int:month>/<int:year>/<str:time_zone> и пользователь пингует /post/11/2021/PST, тогда оно должно вернуть 10 сообщений из ноября. Как вернуть все сообщения за месяц и год в заданном часовом поясе, месяце и году?

Примечание: Сложный крайний случай, который следует учитывать, - это если они публикуют информацию в самый последний день месяца с большим опозданием. В зависимости от часового пояса что-то вроде 12/31/2021 в UTC может быть 01/01/2022.

Установка:

  • Django 3.2.9
  • Postgresql

Я бы попробовал что-то вроде этого:

Предположим, что вы передаете заданный месяц как given_month и данный год как given_year и заданный часовой пояс как given_timezone

Model_Name.objects.filter(created.split('/',4)[2]=given_month,created.split('/',4)[3]=given_year, created.split('/',4)[4]=given_timezone)

Это должно получить значение из раздела месяца, года и timzone вашего сообщения. Возможно, вам придется поиграть с тем, что я вам дал. Также может быть лучше добавить связь от пользователя к посту, чтобы вы могли отфильтровать пользователя для поиска постов с моим ответом. В большинстве случаев это будет намного эффективнее, если предположить, что постов больше, чем пользователей. Это должно вывести вас на правильный путь.

Взять первый момент месяца (полночь 1-го числа) в UTC и первый момент следующего месяца в UTC, скорректировать их с нужным вам часовым поясом, сделать запрос posted__range=(a, b)?

Это может сработать (но математика дат сложна...).

Для этого необходимо python-dateutil сделать вычисление end времени надежным.

from dateutil.relativedelta import relativedelta
from django.utils import timezone

year = 2021
month = 6
tz = datetime.tzinfo("PST")

start = datetime.datetime(year, month, 1)
end = start + relativedelta(months=1)

posts = Post.objects.filter(created__range=(
  timezone.make_aware(start, tz),
  timezone.make_aware(end, tz),
))

Рассмотрите возможность сохранения записи в UTC времени даты, не пытайтесь сохранить смещение часового пояса. Просто запишите время поступления данных на сервер в UTC, и пусть браузер или ваше приложение переведет его в свой настроенный часовой пояс. Не имеет значения, если пользователь в Китае создал сообщение, а на следующий день переехал в Нью-Йорк и прочитал ранее созданное сообщение, время создания_даты будет одинаковым для обоих мест, поскольку оно универсально. Таким образом, если пользователь B захочет восстановить данные, записанные пользователем A с октября, это будет так же просто, как запросить created_at::date>="2021-10-01"

Простите, если это не совсем соответствует вашим ожиданиям, но Django создает поле PostgreSQL timestamp with a time zone для поля DateTimeField.

# \d posts_post
                       Table "public.posts_post"
   Column   |           Type           | Collation | Nullable | Default
------------+--------------------------+-----------+----------+---------
 uuid       | uuid                     |           | not null |
 created    | timestamp with time zone |           | not null |
 updated_at | timestamp with time zone |           |          |
 body       | character varying(500)   |           | not null |
Indexes:
    "posts_post_pkey" PRIMARY KEY, btree (uuid)

Значит, вы можете запрашивать их как временные метки с помощью класса пользовательского поля из Поля временных меток в django.

from django.db import models
from datetime import datetime
from time import strftime

class UnixTimestampField(models.DateTimeField):
    """UnixTimestampField: creates a DateTimeField that is represented on the
    database as a TIMESTAMP field rather than the usual DATETIME field.
    """
    def __init__(self, null=False, blank=False, **kwargs):
        super(UnixTimestampField, self).__init__(**kwargs)
        # default for TIMESTAMP is NOT NULL unlike most fields, so we have to
        # cheat a little:
        self.blank, self.isnull = blank, null
        self.null = True # To prevent the framework from shoving in "not null".

    def db_type(self, connection):
        typ=['TIMESTAMP']
        # See above!
        if self.isnull:
            typ += ['NULL']
        if self.auto_created:
            typ += ['default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP']
        return ' '.join(typ)

    def to_python(self, value):
        if isinstance(value, int):
            return datetime.fromtimestamp(value)
        else:
            return models.DateTimeField.to_python(self, value)

    def get_db_prep_value(self, value, connection, prepared=False):
        if value==None:
            return None
        # Use '%Y%m%d%H%M%S' for MySQL < 4.1
        return strftime('%Y-%m-%d %H:%M:%S',value.timetuple())

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

class Post(models.Model):
    uuid = models.UUIDField(primary_key=True)
    created = UnixTimestampField(verbose_name='Created at', auto_now_add=True)
    updated_at = UnixTimestampField(verbose_name='Last updated at', auto_now=True, blank=True, null=True)


from datetime import datetime, timezone

Post.objects.filter(created__gte=datetime(year=2021, month=11, day=1, tzinfo=timezone.utc).timestamp(), created__lt=datetime(year=2021, month=12, day=1, tzinfo=timezone.utc).timestamp())
Вернуться на верх