diff --git a/src/pure.js b/src/pure.js index 9fd74e6..6f610aa 100644 --- a/src/pure.js +++ b/src/pure.js @@ -50,3 +50,10 @@ export function computeEstimate(current, target, perTrain, perDay) { return { remaining, trainsToGo, days, eta }; } + +const THIRTY_DAYS_MS = 30 * MS_PER_DAY; + +export function pruneHistory(entries, now = Date.now()) { + const cutoff = now - THIRTY_DAYS_MS; + return entries.filter((e) => e.ts > cutoff); +} diff --git a/tests/pure.test.js b/tests/pure.test.js index 3b14f78..76f5cb2 100644 --- a/tests/pure.test.js +++ b/tests/pure.test.js @@ -1,6 +1,9 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { parseTarget, computeEstimate } from '../src/pure.js'; +import { parseTarget, computeEstimate, pruneHistory } from '../src/pure.js'; + +const DAY = 86_400_000; +const NOW = 1_700_000_000_000; // fixed reference test('parseTarget: integer numbers', () => { assert.equal(parseTarget(25), 25); @@ -67,3 +70,27 @@ test('computeEstimate: zero perTrain or perDay does not crash', () => { assert.equal(b.days, 0); assert.equal(b.eta, null); }); + +test('pruneHistory: keeps entries within 30 days, drops older', () => { + const entries = [ + { ts: NOW, delta: 100 }, + { ts: NOW - 1 * DAY, delta: 100 }, + { ts: NOW - 29 * DAY, delta: 100 }, + { ts: NOW - 30 * DAY, delta: 100 }, // exactly 30 days, dropped (strict <) + { ts: NOW - 31 * DAY, delta: 100 }, + ]; + const out = pruneHistory(entries, NOW); + assert.equal(out.length, 3); + assert.deepEqual(out.map((e) => e.ts), [NOW, NOW - 1 * DAY, NOW - 29 * DAY]); +}); + +test('pruneHistory: does not mutate input', () => { + const entries = [{ ts: NOW - 31 * DAY, delta: 100 }]; + const copy = [...entries]; + pruneHistory(entries, NOW); + assert.deepEqual(entries, copy); +}); + +test('pruneHistory: empty input', () => { + assert.deepEqual(pruneHistory([], NOW), []); +});