Добавянето на месеци към дата е по-трудно, отколкото звучи

Добавянето на 7 дни към дата е просто. Взимате число, добавяте 7, готово. Добавянето на месец е съвсем различен проблем.

Проблемът е, че месеците имат различна дължина. Януари има 31 дни. Февруари има 28, понякога 29. Ако сте на 31 януари и добавите един месец, стигате до 31 февруари — което не съществува.

Всяка библиотека за календари, електронна таблица и база данни има мнение за това какво да прави след това.

Какво правят повечето инструменти

Най-честото поведение е да се прескочи до последния ден на целевия месец. 31 януари + 1 месец = 28 февруари (или 29 в преходна година). 31 март + 1 месец = 30 април.

Това се нарича ограничаване до края на месеца и именно това правят 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-дневен цикъл на таксуване и месечен цикъл на таксуване не са едно и също нещо и се разминават бързо с времето.

Какво да проверите в своя инструмент или библиотека

Преди да разчитате на добавяне на дати в каквато и да е система, струва си да знаете:

  • Ограничава ли или препълва при дати в края на месеца?
  • Запазва ли деня от месеца при множество добавяния или отново ограничава всеки път?
  • За крайни случаи при преходна година, какво се случва с 29 фев. + 1 година?

Калкулаторът за дати показва точния резултат за всяка дата и отместване — полезно за проверка на работоспособността преди да утвърдите изчисление в код.