Таймзоны и дедлайн, который дрейфует
Положите дедлайны на сетку месяца - и вы встретите этот баг. Карточка должна быть готова двенадцатого. Кто-то открывает доску из другой страны, и она стоит на одиннадцатом. Никто ничего не набрал неправильно. Оба экрана показывают правду о разных вопросах.
Две разные вещи, которые называют датой
Момент - это точка на мировой шкале времени. Он один и тот же для всех, а вот называется по-разному в зависимости от того, где вы стоите. Девять вечера в одном месте - следующее утро в другом.
Календарный день - это ярлык, о котором люди договорились локально. «Сдать в пятницу» не означает момент. Означает клетку на сетке, и это одна и та же клетка независимо от того, кто смотрит.
Почти каждая библиотека дат, колонка базы и дефолт API дают первое. Почти каждый дедлайн, который набирает человек, - второе. Баг - в этой конверсии, и он тихий, потому что значение никогда не ошибается сильно. Оно ошибается на одну клетку, на границе, у части ваших пользователей.
Где это реально ломается
Не в хранении. Хранение - легко, когда решено.
Ломается на краях, в местах, где конверсия происходит без чьего-либо выбора. Датапикер, отдающий полночь локального времени, которую сериализуют в UTC. Сервер, форматирующий день для письма своей таймзоной, то есть той, с которой собрали контейнер. Сравнение, спрашивающее, прошёл ли дедлайн, сверкой с «прямо сейчас», когда человек на самом деле спрашивает, прошло ли сегодня за ту клетку.
Каждое из этого - одна строка, каждое по отдельности оправдано, а вместе они дают дедлайн, дрейфующий в зависимости от того, кто открыл доску.
Правило, на котором я останавливаюсь
Если вещь - момент, храните момент: создано, отредактировано, отправлено. Это настоящие точки времени, и конвертировать их для показа - правильное поведение.
Если вещь - день, который выбрал человек, храните день. Голую календарную дату, без времени и без зоны, потому что приложить их - выдумать информацию, которой человек не давал. В «сдать двенадцатого» нет часа, и в момент, когда вы добавляете к нему полночь, вы что-то сочинили, а потом сконвертировали собственное сочинение.
Просрочено тогда значит сравнить сегодняшнюю дату в календаре читателя с тем днём. Два ярлыка, одно сравнение, ничего не сконвертировано.
Звучит занудно ровно до первого раза, когда кто-то пропустит дедлайн, который интерфейс показал ему на день раньше.