mirror of https://github.com/ghostfolio/ghostfolio
7 changed files with 368 additions and 284 deletions
@ -1,50 +0,0 @@ |
|||
import { |
|||
getIntradayChartItems, |
|||
INTRADAY_SAMPLE_INTERVAL_MS, |
|||
loadIntradaySamples, |
|||
recordIntradaySample |
|||
} from './intraday-samples'; |
|||
|
|||
describe('intraday samples', () => { |
|||
const now = new Date(2026, 9, 21, 10, 0, 0); |
|||
|
|||
beforeEach(() => { |
|||
localStorage.clear(); |
|||
}); |
|||
|
|||
it('records at most one sample per minute', () => { |
|||
recordIntradaySample({ now, value: 1 }); |
|||
recordIntradaySample({ now: new Date(now.getTime() + 5_000), value: 2 }); |
|||
|
|||
expect(loadIntradaySamples(now)).toEqual([ |
|||
{ timestamp: now.getTime(), value: 2 } |
|||
]); |
|||
|
|||
recordIntradaySample({ |
|||
now: new Date(now.getTime() + INTRADAY_SAMPLE_INTERVAL_MS), |
|||
value: 3 |
|||
}); |
|||
|
|||
expect(loadIntradaySamples(now)).toHaveLength(2); |
|||
}); |
|||
|
|||
it('removes the samples of previous days', () => { |
|||
recordIntradaySample({ now: new Date(2026, 9, 20, 15, 0), value: 1 }); |
|||
recordIntradaySample({ now, value: 2 }); |
|||
|
|||
expect(loadIntradaySamples(new Date(2026, 9, 20))).toEqual([]); |
|||
expect(loadIntradaySamples(now)).toHaveLength(1); |
|||
}); |
|||
|
|||
it('starts the chart of the day with the baseline at midnight', () => { |
|||
const items = getIntradayChartItems({ |
|||
now, |
|||
baseline: 0, |
|||
samples: [{ timestamp: now.getTime(), value: 0.5 }] |
|||
}); |
|||
|
|||
expect(items).toHaveLength(2); |
|||
expect(new Date(items[0].date).getHours()).toBe(0); |
|||
expect(items[1].value).toBe(0.5); |
|||
}); |
|||
}); |
|||
@ -1,83 +0,0 @@ |
|||
import { LineChartItem } from '@ghostfolio/common/interfaces'; |
|||
|
|||
import { format, startOfDay } from 'date-fns'; |
|||
|
|||
export const INTRADAY_SAMPLES_KEY_PREFIX = 'gf-intraday-'; |
|||
export const INTRADAY_SAMPLE_INTERVAL_MS = 60_000; |
|||
|
|||
export interface IntradaySample { |
|||
timestamp: number; |
|||
value: number; |
|||
} |
|||
|
|||
function getKey(aDate: Date) { |
|||
return `${INTRADAY_SAMPLES_KEY_PREFIX}${format(aDate, 'yyyy-MM-dd')}`; |
|||
} |
|||
|
|||
export function loadIntradaySamples(aDate = new Date()): IntradaySample[] { |
|||
try { |
|||
const samples = JSON.parse(localStorage.getItem(getKey(aDate)) ?? '[]'); |
|||
|
|||
return Array.isArray(samples) ? samples : []; |
|||
} catch { |
|||
return []; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Stores the current value of the day (at most one sample per minute) in the |
|||
* local storage of the device, because only a single value per day is stored |
|||
* on the server. Samples of previous days are removed. |
|||
*/ |
|||
export function recordIntradaySample({ |
|||
now = new Date(), |
|||
value |
|||
}: { |
|||
now?: Date; |
|||
value: number; |
|||
}): IntradaySample[] { |
|||
const samples = loadIntradaySamples(now); |
|||
const last = samples.at(-1); |
|||
|
|||
if (!last || now.getTime() - last.timestamp >= INTRADAY_SAMPLE_INTERVAL_MS) { |
|||
samples.push({ value, timestamp: now.getTime() }); |
|||
} else { |
|||
// Keep the latest value of the current minute
|
|||
last.value = value; |
|||
} |
|||
|
|||
try { |
|||
for (let index = localStorage.length - 1; index >= 0; index--) { |
|||
const key = localStorage.key(index); |
|||
|
|||
if (key?.startsWith(INTRADAY_SAMPLES_KEY_PREFIX) && key !== getKey(now)) { |
|||
localStorage.removeItem(key); |
|||
} |
|||
} |
|||
|
|||
localStorage.setItem(getKey(now), JSON.stringify(samples)); |
|||
} catch {} |
|||
|
|||
return samples; |
|||
} |
|||
|
|||
/** |
|||
* Builds the chart of the current day: the baseline (the close of the previous |
|||
* day) at the start of the day followed by the recorded samples of the day. |
|||
*/ |
|||
export function getIntradayChartItems({ |
|||
baseline, |
|||
now = new Date(), |
|||
samples |
|||
}: { |
|||
baseline: number; |
|||
now?: Date; |
|||
samples: IntradaySample[]; |
|||
}): LineChartItem[] { |
|||
return [ |
|||
{ date: startOfDay(now).toISOString(), value: baseline }, |
|||
...samples.map(({ timestamp, value }) => { |
|||
return { value, date: new Date(timestamp).toISOString() }; |
|||
}) |
|||
]; |
|||
} |
|||
Loading…
Reference in new issue