Границы даты рождения кажутся темой для учебника, пока форма не начинает считать возраст сама. Тогда выясняется, что 29 февраля существует не каждый год, что «сегодня» в двух системах может означать разные сутки, а подставленное первое января живёт в базе как настоящий день рождения. Ниже разберём крайние случаи, которые стоит держать в тестовом наборе, и объясним, почему возраст нельзя считать вычитанием двух отметок времени.
Високосный год и 29 февраля
Правило високосного года простое, но с оговоркой, о которой часто забывают: год делится на четыре, однако у вековых годов нужно ещё деление на четыреста. Поэтому 1900 год високосным не был, а 2000 год был, хотя оба делятся на четыре.
Практическое следствие — дата 29 февраля. Люди, родившиеся в этот день, существуют, и в их документах стоит именно эта дата. Форма, которая считает такую дату недопустимой, отказывает реальным пользователям. Система, которая хранит дату в виде текста и не проверяет её согласованность с календарём, наоборот, спокойно принимает тридцатое февраля. Оба поведения — дефект, просто разной природы.
Проверка календарной даты полезна и сама по себе. Поле, которое знает, сколько дней в месяце и как устроен високосный год, отсеивает опечатки вроде тридцать первого апреля ещё до отправки формы. Отсеивать при этом нужно ровно невозможные даты: запрет на 29 февраля — это уже не проверка календаря, а выдуманное правило, которое отвергает существующих людей.
Отдельный случай — обработка дня рождения человека, родившегося 29 февраля, в невисокосный год. Законодательство разных стран решает это по-разному, и универсального ответа нет; на практике приложение должно уметь хотя бы не падать и не показывать возраст, отличающийся на год от ожидаемого.
Как считаются полные годы
Возраст в полных годах считается не вычитанием лет, а сравнением дня и месяца. Человек, родившийся в середине года, становится на год старше только тогда, когда наступает соответствующая дата, а не первого января. Формула проста: из текущего года вычитается год рождения, и если день рождения в этом году ещё не наступил, результат уменьшается на единицу.
Граница здесь проходит по одному дню, и именно поэтому она интересна для тестирования. Разница между «исполнилось вчера» и «исполнится завтра» настолько мала, что её обычно и не проверяют, а между тем почти все ошибки в подсчёте возраста живут ровно в этой точке.
Считается ли человек совершеннолетним в день рождения?
Да, в этот день полные годы уже наступили. Если человеку исполняется восемнадцать сегодня, то сегодня он считается восемнадцатилетним в большинстве систем, которые считают возраст по календарю. Это значит, что день рождения — идеальный тестовый случай: форма, которая в этот день ещё отказывает, ошибётся на целом дне и заметит это только при жалобе.
Не менее полезен случай «остался один день»: пользователь, которому завтра исполняется нужный возраст, сегодня должен получить отказ, и отказ вежливый. Как устроены сами пороги и откуда берутся разные числа, подробно разобрано в статье про проверку возраста.
| Ситуация | Ожидаемое поведение |
|---|---|
| Исполнилось сегодня | порог считается пройденным |
| Остался один день | порог ещё не пройден, понятное объяснение |
| Дата рождения 29 февраля | дата остаётся допустимой |
| Дата в будущем | отказ с отдельным сообщением |
Почему сегодня зависит от часового пояса?
Потому что дата без времени не существует отдельно от места. Когда в одной стране уже наступило следующее число, в другой ещё идёт предыдущее. Сервер, который вычисляет возраст по своей зоне, а пользователь, который видит результат в своей, могут разойтись на сутки — и в этот промежуток один скажет «доступно», а другой «ещё нет».
Опасны и искусственные скачки. Смена часового пояса, переход на летнее время и простое обнуление системных часов сдвигают момент, в который приложение решает, наступил ли день рождения. Если тест сравнивает возраст, рассчитанный в разных зонах, он выявит эту разницу раньше пользователя.
Календари, которые идут иначе, и подставные даты
Привычный календарь не единственный. Эфиопия, Непал и Иран ведут собственные календарные системы, и одна и та же дата в них записывается иначе и имеет другой номер года. Если форма принимает только одну систему, пользователь либо вводит дату с чужих слов, либо не может ввести её вовсе. Для тестовых данных это означает, что стоит проверять не только привычный формат, но и поведение поля при неожиданных значениях.
Вторая ловушка — подставные значения. Когда обязательное поле нечего заполнить, в него часто ставят первое января или год, который выглядит очевидно служебным. Через месяц такая дата уже ничем не отличается от настоящей: отчёты считают по ней возраст, а генератор случайных записей тиражирует её дальше. Гораздо честнее оставить поле пустым или хранить явный признак «дата неизвестна», чем прятать отсутствие данных за правдоподобным числом.
Как получить тестовую дату рождения
В генераторе данных личности дата рождения выходит правдоподобной для выбранной страны и согласованной с остальными полями записи. Ключ личности позволяет закрепить конкретную дату, если тесту нужен один и тот же образец при каждом запуске.
Значения синтетические: они не относятся к реальным людям и предназначены исключительно для тестирования. Выдавать их за настоящий день рождения или использовать, чтобы выдать себя за человека, нельзя.
Что важно разработчику: хранение и границы
Храните дату рождения как календарную дату без времени и без часового пояса. Момент времени и день рождения — разные сущности, и если первое подставить вместо второго, лишний час в конце суток превратит человека на день старше или младше в зависимости от зоны сервера.
Отдельно стоит сказать про разбор строки. Одна и та же пара чисел читается как день и месяц или как месяц и день в зависимости от страны, и по внешнему виду значения это не определяется. Принимайте дату в известном формате, который задаёт само поле или выбранная локаль, а не пытайтесь угадать его: случай, где первое число больше двенадцати, ещё подсказывает ответ, но все остальные неотличимы, и половина из них будет разобрана неверно.
Считайте возраст по частям даты, а не вычитанием отметок времени: количество дней, поделённое на среднее число дней в году, даёт правдоподобное число и неверный ответ на границе. Если возраст нужен только для сравнения с порогом, сравнивайте даты: порог задаётся как дата, до которой человек должен родиться.
И держите в тестах фиксированное «сегодня». Проверка, которая обращается к системным часам, завтра может повести себя иначе, а через год перестанет воспроизводиться вовсе. Передавайте дату в проверку явно, и граничные случаи — день рождения, канун, 29 февраля — станут стабильными и воспроизводимыми. О том, как такие фиксированные записи уживаются с остальными полями набора, рассказано в материале про согласованность полей.
Что делать дальше
Добавьте в набор три даты: сегодняшнюю, вчерашнюю и ту, что наступает завтра относительно порога, который вы проверяете, а четвёртой сделайте 29 февраля. Сгенерируйте остальные записи в генераторе и убедитесь, что возраст, который показывает интерфейс, совпадает с тем, что вы посчитали вручную.