Я работаю над приложением, которое, помимо прочего, имеет некоторые функции управления заказами, и я обнаружил проблему, которая, по моему мнению, связана с часовыми поясами.
Справочная информация
Бэкэнд написан на Python, и мы используем Flask и SQLAlchemy. База данных PostgreSQL. Мы (и пользователи, и разработчики) находимся в одном часовом поясе (CET / CEST). Рассматриваемое приложение размещено на AWS с серверами в Ирландии, как мне кажется.
Общее описание проблемы
Когда я получаю заказы и пытаюсь отфильтровать их по определенному диапазону дат, заказы с первого часа следующего дня (вне диапазона дат) включается в результат.
Пошаговая инструкция
- Интерфейс отправляет запрос в бэкэнд Python с диапазоном дат в строке запроса.
- Проект Flask получает запрос, извлекает параметры в строке запроса и вызывает функцию get_orders в OrderService.
- get_order создает объект SQLAlchemy Query и фильтрует по заданным параметрам.
# Very simplified version of actual code
def get_orders(self, site_id, params):
created_after = params.get('created_after')
created_before = params.get('created_before')
query = self.sess.query(Order) \
.filter(Order.site_id == site_id)
if created_after:
dt = datetime.strptime(created_after, '%Y%m%d-%H%M%S-%f')
query = query.filter(Order.created_at > self._nor_to_utc_tz(dt))
if created_before:
dt = datetime.strptime(created_before, '%Y%m%d-%H%M%S-%f')
query = query.filter(Order.created_at < self._nor_to_utz_tz(dt))
...
return result
def _nor_to_utc_tz(dt):
# Incoming datetime is always the same timezone
dt = dt.replace(tzinfo=tz.gettz('Europe/Oslo'))
# All datetimes is stored as UTC on server
dt = dt.astimezone(tz.gettz('UTC'))
return dt
Результат запроса преобразуется в диктовку и возвращается в чертеж Flask. Проект возвращает диктант в виде JSON на внешний интерфейс.
Шаблон модели SQLAlchemy
class Order(Base, ModelTemplate):
__tablename__ = 'orders'
...
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
...
Результаты
При локальном тестировании все работает нормально. Я сделал тестовые заказы на все часы дня, и они отфильтрованы по правильному диапазону дат.
Проблема возникает в производственной среде. Заказы, созданные сразу после полуночи по местному времени, включаются в результат для запросов, которые должны были быть ограничены заказами, созданными накануне.
Пример
- Кто-то покупаетчто-то 2 октября в 00:55 (UTC + 2).
- Заказ создается и сохраняется с отметкой времени 2019-10-01 22:55 (UTC + 0).
- Позже кто-то хочет просмотреть все заказы 2 октября и отправляет запрос навсе заказы, созданные после 2019-10-02 00:00:00 и до 2019-10-02 23:59:59.
- Бэкэнд преобразует это время в UTC + 0, так что должны быть возвращены все заказы, созданные между 2019-10-01 22:00:00 и 2019-10-02 21:59:59, но вышеупомянутый заказне является.
Теория
Моя теория, основанная только на том, что я не могу думать ни о чем другом, что соответствует шаблону, заключается в том, что postgres конвертируетдиапазон дат по местному времени серверов перед фильтрацией результата. Я СЧИТАЮ (или размышляю), что местное время сервера - UTC + 1 (начиная с Ирландии), что соответствует полученному результату.
То, что я пробовал
- Удаление информации о часовом поясе из даты и времени после преобразования из норвежского в UTC часового пояса, в случае, если обманул postgres, полагая, что dt должно бытьпереведено в местное время
- Добавление timezone = True к объекту DateTime в модели SQLAlchemy, чтобы сделать его временной меткой с часовым поясом в postgres, а затем добавить норвежский часовой пояс к datetime, используемому для фильтрации запроса (и не преобразовывать его в UTC+0, надеясь, что Postgres сделает это).
- Разочарован.