Как сохранить дни рождения в базе данных с поддержкой часовых поясов? - PullRequest
0 голосов
/ 02 августа 2020

Я использую JavaScript и PostgreSQL базу данных, я хотел бы хранить дни рождения и уведомлять пользователей в 12:00 в их собственном часовом поясе, в настоящее время я конвертирую даты из их часового пояса в время моего локального сервера и проверяю каждый час, чтобы посмотрите, совпадают ли дата и время

import { parseFromTimeZone } from "date-fns-timezone";

const userInput = "08-11" // day/month
const timeZone = "Europe/Amsterdam"
const date = parseFromTimeZone(`2000-${userInput} 00:00:00`, { timeZone });

// This is what I store in my database
const dateToStore = date.toISOString().slice("2000:".length).split(":")[0];

// This is what I run every hour
await Birthday.find({
  where: {
    date: new Date().toISOString().slice("year:".length).split(":")[0],
  },
});

Проблема в том, что это решение не очень динамично c, потому что, если я перенесу свой сервер, он сломается, мои вопросы:

  • Как я могу сохранить дни рождения? Предположим, что пользователи указывают месяц, день и часовой пояс
  • В каком интервале я могу / должен проверять, следует ли отправлять сообщение о дне рождения? (00:00) в часовом поясе пользователя и указанной дате
  • Как бы выглядела эта проверка?

У меня есть дата-fns, но я не против использовать другие библиотеки

1 Ответ

0 голосов
/ 02 августа 2020

Я бы порекомендовал решение с таблицей account, содержащей три поля:

  1. birthday, который имеет тип Postgres date.
  2. timezone, из Postgres типа text. Здесь вы должны хранить что-то вроде Europe/Amsterdam, причем важная часть состоит в том, что это что-то Postgres, и все ваши библиотеки дат могут распознать часовой пояс.
  3. last_birthday_wish_sent_at типа timestamptz (сокращение для timestamp with time zone, который хранит все внутри как UT C).

Я отделил дату дня рождения от часового пояса, потому что помню, что день рождения пользователя всегда совпадает с днем ​​в любой точке мира , даже если они двигаются. Так что, если мой день рождения 11 августа в Амстердаме, это по-прежнему 11 августа, если я перееду в Сан-Франциско. Хранение этих компонентов по отдельности позволит вам перенастроить их часовой пояс, если они перемещаются.

Я бы запускал cron на 0-й минуте каждого часа, который запускал logi c что-то вроде этого (псевдокод, извините):

for timezone in all timezones:
     if > 12 PM in timezone:
         for account in accounts in timezone:
             if birthday <= today AND (last_birthday_wish_sent_at IS NULL OR last_birthday_wish_sent_at < now() - '1 year):
                 send birthday wish
                 set last_birthday_wish_sent_at = now()

Цель last_birthday_wish_sent_at состоит в том, чтобы вы могли написать алгоритм, который немного тупее и более устойчив (т.е. пожелания дня рождения все равно отправляются, даже если cron выходит из строя один час), но все же убедитесь, что никогда не отправляйте дважды дату рождения wi sh для любого года.

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

Вы бы хотели смоделировать выбор и фильтрацию учетной записи в псевдокоде выше как SQL, чтобы вы не возвращали наборы результатов, превышающие необходимые. Что-то вроде:

SELECT *
FROM account
WHERE timezone IN ('Europe/Amsterdam', ...)
    -- note: actual date comparison a little more complicated than this
    -- because you should make sure to compare the month and day components
    -- only (probably with the `EXTRACT` function)
    AND birthday <= NOW()
    AND (
        last_birthday_wish_sent IS NULL
        OR last_birthday_wish_sent < NOW() - '1 year'::interval
    );

И убедитесь, что есть соответствующие индексы на timezone, birthday и last_birthday_wish_sent.

Вы также можете подтянуть logi c на время проверка зоны: он всегда где-то превращается в 12 часов вечера, но это совершенно предсказуемо относительно того, где это происходит, поэтому нет необходимости каждый раз проверять каждый часовой пояс. Вы также можете преобразовать sh это в Postgres и получить весь лог c выбора, упакованный в один запрос.

...