Skip to content

Commit b7dfa18

Browse files
committed
Release 2.5.7: clamp expected day to last day of month
1 parent 9595057 commit b7dfa18

2 files changed

Lines changed: 19 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 2.5.7: 2026-04-19
2+
3+
* Clamp due day to last day of month so day 31 incomes/bills work in shorter months
4+
15
### 2.5.6: 2026-04-17
26

37
* Stop reserving obligations from current balance when upcoming income covers them

src/lib/daily-budget.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,23 @@ export function calculateDailyBudget(params: {
5656
// This prevents spending everything before a small income and starving the next period
5757
const minWindowDays = 14;
5858

59+
// Clamp day to last day of month so e.g. expected_day=31 in April resolves to 30
60+
const clampToMonth = (day: number) => Math.min(resolveDay(day), daysInMonth);
61+
5962
// Collect all income events with absolute day positions
6063
const incomeEvents: { absDay: number; amount: number }[] = [];
6164
for (const i of unreceivedIncomes) {
62-
const d = resolveDay(i.expectedDay);
63-
if (d > today && d <= daysInMonth) {
65+
const d = clampToMonth(i.expectedDay);
66+
if (d > today) {
6467
incomeEvents.push({ absDay: d, amount: i.amount });
6568
}
6669
}
6770

6871
// Add next month's income events if window extends past month end
6972
if (allIncomes) {
70-
const allDays = allIncomes.map((i) => resolveDay(i.expectedDay)).sort((a, b) => a - b);
71-
for (const d of allDays) {
72-
incomeEvents.push({ absDay: daysInMonth + d, amount: allIncomes.find((i) => resolveDay(i.expectedDay) === d)!.amount });
73+
for (const i of allIncomes) {
74+
const d = clampToMonth(i.expectedDay);
75+
incomeEvents.push({ absDay: daysInMonth + d, amount: i.amount });
7376
}
7477
}
7578
incomeEvents.sort((a, b) => a.absDay - b.absDay);
@@ -80,16 +83,18 @@ export function calculateDailyBudget(params: {
8083
const totalDays = endAbsDay - today;
8184
if (totalDays <= 0) return { dailyBudget: 0, tightestSegment: null, segmentCount: 0 };
8285

83-
// Collect obligations in window with absolute due day
86+
// Collect obligations in window with absolute due day (clamp to last day of month)
8487
const obligationEvents: { absDay: number; amount: number }[] = [];
8588
for (const b of unpaidBills) {
86-
if (b.dueDay > today && b.dueDay <= Math.min(daysInMonth, endAbsDay)) {
87-
obligationEvents.push({ absDay: b.dueDay, amount: b.amount });
89+
const d = clampToMonth(b.dueDay);
90+
if (d > today && d <= endAbsDay) {
91+
obligationEvents.push({ absDay: d, amount: b.amount });
8892
}
8993
}
9094
for (const d of debts) {
91-
if (d.dueDay > today && d.dueDay <= Math.min(daysInMonth, endAbsDay) && d.amount > 0) {
92-
obligationEvents.push({ absDay: d.dueDay, amount: d.amount });
95+
const day = clampToMonth(d.dueDay);
96+
if (day > today && day <= endAbsDay && d.amount > 0) {
97+
obligationEvents.push({ absDay: day, amount: d.amount });
9398
}
9499
}
95100
if (endAbsDay > daysInMonth) {

0 commit comments

Comments
 (0)