Skip to content

Latest commit

 

History

History
137 lines (85 loc) · 17.8 KB

File metadata and controls

137 lines (85 loc) · 17.8 KB

Дата и время в клиент серверных приложениях

Введение

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

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

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


Определение абсолютного и относительного времени

Работая с датами и временем, мы часто сталкиваемся с концепциями, которые, кажется, просты и понятны, но могут вносить путаницу без четкого определения. Особенно это касается понимания часовых поясов и взаимосвязи между местным и универсальным временем. Прежде чем углубиться в эту тему, введем два ключевых понятия: абсолютное и относительное время.

Абсолютное время — это время, которое не зависит от часовых поясов, географического положения или индивидуальных предпочтений. Оно едино и неизменно для всего мира в каждый конкретный момент, а потому оно абсолютно. Это своего рода "базовое" время, часто ассоциируемое со Скоординированным Всемирным Временем (UTC).

Относительное время — в отличие от абсолютного, относится к конкретному часовому поясу и зависит от географического расположения. Оно отражает реальное местное время, адаптированное к условиям конкретного региона. Например, когда по UTC 12:00, в Москве будет 15:00 (учитывая разницу в 3 часа).

Таким образом:

  1. Абсолютное время — единый стандарт, на основе которого происходит синхронизация времени по всему миру, он абсолютен, так как не зависит от внешних факторов и является универсальным для всех точек мира.
  2. Относительное время — это местное время, которое относится к конкретному месту и соответствует реальному дню и ночи в данной локации.

Что там с данными?

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

В базе данных PostgreSQL:

  1. Абсолютное время (с информацией о часовом поясе):

    • timestamp with time zone (обычно аббревиатура timestamptz): Хранит момент времени, который учитывает часовой пояс. PostgreSQL автоматически преобразует timestamptz в UTC при сохранении и обратно в локальное время при извлечении на основе настроек сервера или сессии.
  2. Относительное время (без информации о часовом поясе):

    • timestamp without time zone (обычно аббревиатура timestamp): Хранит момент времени без учета часового пояса. Это просто дата и время, без относительности к какому-либо часовому поясу.
    • date: Хранит только дату без времени и часового пояса.
    • time: Хранит только время без даты и часового пояса.

Дополнительно про отличия timestamp и timestamptz

Timestamp

Timestamp в PostgreSQL сохраняет дату и время исключительно как они были введены, не привязывая их к определенному часовому поясу. Это значит, что если в базе данных сохранен timestamp как 2023-09-25 12:00:00, он всегда будет отображаться именно так, не зависимо от настроек часового пояса сервера или сессии пользователя.

Пример: Предположим, у нас есть timestamp 2023-09-25 12:00:00. Если часовой пояс сервера GMT+3, то значение всё равно останется 2023-09-25 12:00:00.

Timestamptz

В отличие от timestamp, timestamptz учитывает часовой пояс, конвертируя введенное значение времени в UTC при сохранении. При запросе к базе данных PostgreSQL конвертирует это время из UTC в часовой пояс сервера или сессии, на основе текущих настроек.

Пример: Записываем 2023-09-25 12:00:00 как timestamptz в часовом поясе GMT+3. PostgreSQL сохранит это значение как 2023-09-25 09:00:00 UTC. При запросе, если часовой пояс сервера GMT+5, значение будет отображаться как 2023-09-25 14:00:00.

Однако время из такого поля можно запросить и в UTC формате, тогда оно будет иметь формат 2023-09-25T09:00:00Z где Z в конце указывает, что дата указана в UTC.

Часовой пояс по умолчанию и настройки:

По умолчанию, timestamptz отображает время в часовом поясе сервера, однако, его можно перенастроить для отображения в другом часовом поясе, используя параметры сессии или конфигурации сервера.

Итого:

  • Timestamp сохраняет локальное время, без учета часового пояса.
  • Timestamptz сохраняет время в UTC и конвертирует его в локальное время в зависимости от настроек часового пояса при запросе.
  • Настройка часового пояса может быть изменена на уровне сервера или сессии, что влияет на отображение времени для timestamptz.

На уровне API:

  1. Абсолютное время:

    • В формате ISO 8601 с указанием часового пояса, например: 2023-09-23T10:00:00+03:00. Здесь "+03:00" указывает на часовой пояс. Также часто используют формат UTC, например: 2023-09-23T07:00:00Z.
    • В виде Unix time (или Epoch time): это количество секунд, прошедших с полночи 1 января 1970 года по UTC. Например, значение 1601234567 представляет определенный момент времени, который можно преобразовать в удобочитаемый формат.
  2. Относительное время:

    • В формате ISO 8601, но без указания часового пояса, например: 2023-09-23T10:00:00.
    • Только дата в формате YYYY-MM-DD, например: 2023-09-23.
    • Только время в формате HH:MM:SS, например: 10:00:00.

На уровне GraphQL API:

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

  1. Абсолютное время: Важно выбрать один стандарт для абсолютного времени, чтобы обеспечить единообразие во всей системе.

    • DateTimeUtc: Тип, представляющий время в формате ISO 8601 в UTC. Например, 2020-04-20T16:20:04.000000Z.
    • DateTimeWithZone: Тип, представляющий время в формате ISO 8601 с указанием часового пояса. Например, 2023-09-23T10:00:00+03:00.
    • UnixTimestamp: Тип для представления времени в формате Unix time. Например, 1601234567.
  2. Относительное время:

    • DateTime: Тип для представления даты и времени без часового пояса в формате ISO 8601. Например, 2023-09-23T10:00:00.
    • Date: Тип, представляющий только дату в формате ISO 8601. Например, 2023-09-23.
    • Time: Тип, представляющий только время в соответствии со стандартом ISO 8601. Например, 10:00:00.

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

Пример по выбору типа хранения времени на примере Booking.com

Основная идея: Определить, где и когда выбирать абсолютное или относительное время в зависимости от бизнес-логики.

Скорее всего, если поле времени автоматически устанавливает сервер, то это будет абсолютное время. Если время устанавливает пользователь (мануально), то в большинстве случаев это будет относительное время.

Однако стоит отметить, что в случае если пользователю будет предложено предоставить информацию через сколько он например будет, то нам надо будет работать с абсолютным временем

  1. Отель:

    • created_at: Абсолютная дата и время добавления отеля на платформу. Устанавливается сервером. Тип: DateTimeUtc.
    • updated_at: Абсолютная дата и время последнего изменения информации об отеле. Устанавливается сервером. Тип: DateTimeUtc.
    • check_in_from и check_out_until: Относительное время заезда и выезда в отеле. Устанавливается представителем отеля (мануальный ввод). Тип: Time.
  2. Номер в отеле:

    • availability_dates: Относительные даты доступности номера. Устанавливается на основе дат указанных гостями (мануально) при своих бронированиях. Тип: Date[].
  3. Бронирование:

    • booking_timestamp: Абсолютная дата и время создания бронирования. Устанавливается сервером. Тип: DateTimeUtc.
    • stay_from_date и stay_until_date: Относительные даты пребывания гостя. Устанавливается гостем. Тип: Date.
    • stay_from_time и stay_to_time: Время заезда и выезда гостя. Устанавливается гостем. Тип: Time.

Принцип разделения даты и времени при бронировании:

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

  1. Изменение даты: Нам необходимо пересчитать стоимость бронирования исходя из новых дат, по суточному тарифу

  2. Изменение времени прибытия: проверяем соответствует ли выбранное время стандартам отеля (check_in_from и check_out_until) и, если необходимо, обрабатываем дополнительные сценарии, такие как ранний заезд или дополнительная оплата.

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

Зачем все это нужно?

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

Следуя рекомендациям из статьи, мы избавляемся от следующих проблем:

  1. Исключается влияние серверного времени на то, какое время будет отображаться у пользователя на клиентской стороне.
  2. Нет необходимости конвертировать даты в определенный часовой пояс, так как это учтено заранее.
  3. В целом, вопросы конвертации времени и работы с различными часовыми поясами перестают быть актуальными для разработчика.

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