Додавати місяці до дати складніше, ніж здається
Додати 7 днів до дати просто. Є число, додаєте 7 — готово. Додати місяць — зовсім інша задача.
Проблема в тому, що місяці мають різну тривалість. У січні 31 день. У лютому 28, інколи 29. Якщо ви на 31 січня і додаєте один місяць, ви потрапляєте на 31 лютого — а такої дати не існує.
Кожна календарна бібліотека, таблиця й база даних має свою думку, що робити далі.
Що робить більшість інструментів
Найпоширеніша поведінка — «причепитися» до останнього дня цільового місяця. 31 січня + 1 місяць = 28 лютого (або 29 у високосний рік). 31 березня + 1 місяць = 30 квітня.
Це називається обмеженням кінцем місяця (end-of-month clamping) і саме так за замовчуванням працюють Excel, Google Sheets, dateutil у Python та більшість бібліотек дат.
Це розумно, але створює тонку проблему: операція не є оборотною. Якщо додати один місяць до 31 січня, отримаєте 28 лютого. Якщо потім відняти один місяць, отримаєте 28 січня — не 31 січня. Ви «втратили» три дні.
Підхід із переповненням
Деякі системи дозволяють даті «переповнюватися» в наступний місяць замість обмеження. 31 січня + 1 місяць = 3 березня (або 2 березня у високосний рік, бо в лютому 29 днів).
Це зберігає загальну кількість днів, але результат опиняється в зовсім іншому місяці, ніж ви планували. Це дивує і з точки зору користувача зазвичай неправильно.
Синтаксис INTERVAL у SQL в деяких базах даних може працювати саме так залежно від конфігурації. Легко «обпектися», якщо не знати, яку поведінку ви використовуєте.
Додавання років має ту саму проблему
29 лютого існує лише у високосні роки. Додайте один рік до 29 лютого 2024 року — і отримаєте 29 лютого 2025 року, а такої дати немає. Обмеження дає 28 лютого 2025 року.
Та сама поведінка, ті самі компроміси.
Коли це справді створює баги
Класичний приклад — білінг підписок. Користувач реєструється 31 січня. Наступна дата списання — 28 лютого. Потім 28 березня. Потім 28 квітня. У кожному місяці після першого з нього списують на 2–3 дні раніше, ніж він очікував би.
Повторювані події в календарі мають ту саму проблему. «Щомісяця 31-го» непомітно стає «щомісяця в останній день» для місяців, у яких немає 31-го.
Графіки погашення кредитів, дати виплат зарплати та будь-що з «щомісячною» повторюваністю рано чи пізно впирається в цей крайовий випадок.
Додавання днів не має цієї проблеми
Якщо потрібно виразити «через 30 днів», а не «через один місяць», просто додайте 30 днів. Результат однозначний і оборотний.
Різниця важлива: цикл оплати кожні 30 днів і щомісячний цикл — не одне й те саме, і з часом вони швидко розходяться.
Що перевірити у вашому інструменті або бібліотеці
Перш ніж покладатися на додавання дат у будь-якій системі, варто знати:
- Чи обмежує вона кінець місяця (clamp), чи робить переповнення (overflow) для дат наприкінці місяця?
- Чи зберігає вона день місяця при кількох додаваннях, чи повторно «притискає» кожного разу?
- Для високосних випадків — що відбувається з 29 лютого + 1 рік?
Інструмент Date Calculator показує точний результат для будь-якої дати та зсуву — корисно для перевірки «на здоровий глузд», перш ніж закладати розрахунок у код.