Pievienot mēnešus datumam ir grūtāk, nekā šķiet

Pievienot datumam 7 dienas ir vienkārši. Ir skaitlis, pieskaiti 7 — gatavs. Pievienot mēnesi ir pavisam cita problēma.

Grūtības ir tajā, ka mēnešiem ir atšķirīgs garums. Janvārī ir 31 diena. Februārī ir 28, dažreiz 29. Ja ir 31. janvāris un tu pievieno vienu mēnesi, tu nonāc 31. februārī — kas neeksistē.

Katrai kalendāra bibliotēkai, izklājlapai un datubāzei ir savs viedoklis, ko darīt tālāk.

Ko dara lielākā daļa rīku

Visbiežākā uzvedība ir “piespraust” pie mērķa mēneša pēdējās dienas. 31. janvāris + 1 mēnesis = 28. februāris (vai 29, ja ir garais gads). 31. marts + 1 mēnesis = 30. aprīlis.

To sauc par mēneša beigu piespraušanu (end-of-month clamping), un tā Excel, Google Sheets, Python dateutil un lielākā daļa datumu bibliotēku rīkojas pēc noklusējuma.

Tas ir saprātīgi, bet rada smalku problēmu: darbība nav atgriezeniska. Ja tu 31. janvārim pieskaiti vienu mēnesi, iegūsti 28. februāri. Ja pēc tam atņem vienu mēnesi, iegūsti 28. janvāri — nevis 31. janvāri. Trīs dienas ir “pazudušas”.

Pārplūdes pieeja

Dažas sistēmas ļauj datumam “pārplūst” uz nākamo mēnesi, nevis piesprauž pie pēdējās dienas. 31. janvāris + 1 mēnesis = 3. marts (vai 2. marts garajā gadā, jo februārī ir 29 dienas).

Tas saglabā kopējo dienu skaitu, taču rezultāts iekrīt pavisam citā mēnesī, nekā bija iecerēts. Tas ir pārsteidzoši un no lietotāja viedokļa parasti nepareizi.

Dažās datubāzēs SQL INTERVAL sintakse var darīt tieši to atkarībā no konfigurācijas. Ir viegli “apdedzināties”, ja nezini, ar kādu uzvedību strādā.

Ar gadiem ir tā pati problēma

29. februāris pastāv tikai garajos gados. Pievieno vienu gadu 29. februārim 2024. gadā, un tu iegūsti 29. februāri 2025. gadā — kas neeksistē. Piespraušana dod 28. februāri 2025. gadā.

Tāda pati uzvedība, tādi paši kompromisi.

Kad tas patiesībā rada kļūdas

Klasiskais piemērs ir abonementu rēķini. Lietotājs piesakās 31. janvārī. Nākamais norēķinu datums ir 28. februāris. Pēc tam 28. marts. Pēc tam 28. aprīlis. Katru mēnesi pēc pirmā rēķins tiek izrakstīts par 2–3 dienām agrāk, nekā cilvēks gaidītu.

Arī atkārtoti kalendāra notikumi saskaras ar to pašu. “Katru mēnesi 31.” mēnešos, kuros nav 31 dienas, klusām kļūst par “katru mēnesi pēdējā dienā”.

Kredītu atmaksas grafiki, algu izmaksu datumi un jebkas ar “ikmēneša” regularitāti agrāk vai vēlāk uzduras šai robežsituācijai.

Pievienot dienas nav šīs problēmas

Ja tev jāizsaka “pēc 30 dienām”, nevis “pēc viena mēneša”, vienkārši pieskaiti 30 dienas. Rezultāts ir nepārprotams un atgriezenisks.

Atšķirība ir svarīga: 30 dienu norēķinu cikls un ikmēneša norēķinu cikls nav viens un tas pats, un laika gaitā tie ātri atšķiras.

Ko pārbaudīt savā rīkā vai bibliotēkā

Pirms paļauties uz datumu saskaitīšanu jebkurā sistēmā, ir vērts zināt:

  • Vai mēneša beigu datumos tiek piesprausts vai pārplūst?
  • Vai vairākās saskaitīšanās reizēs tiek saglabāta mēneša diena, vai katru reizi notiek atkārtota piespraušana?
  • Ko dara garo gadu robežgadījumos, piemēram, 29. febr. + 1 gads?

Datuma kalkulators parāda precīzu rezultātu jebkuram datumam un nobīdei — noderīgi, lai pārbaudītu loģiku pirms to izmanto kodā.