55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
/** SRS §9.4.5 — business hours 09:00–18:00, Mon–Fri (local server timezone). */
|
||
|
||
const BUSINESS_START_HOUR = 9;
|
||
const BUSINESS_END_HOUR = 18;
|
||
|
||
export function isBusinessHoursEnabled(): boolean {
|
||
if (process.env.DEBUG_SLA_FAST_MODE === 'true') return false;
|
||
return process.env.SLA_BUSINESS_HOURS !== 'false';
|
||
}
|
||
|
||
export function businessMsBetween(start: Date, end: Date): number {
|
||
if (end.getTime() <= start.getTime()) return 0;
|
||
|
||
let total = 0;
|
||
const cursor = new Date(start);
|
||
|
||
while (cursor.getTime() < end.getTime()) {
|
||
const day = cursor.getDay();
|
||
if (day !== 0 && day !== 6) {
|
||
const windowStart = new Date(cursor);
|
||
windowStart.setHours(BUSINESS_START_HOUR, 0, 0, 0);
|
||
const windowEnd = new Date(cursor);
|
||
windowEnd.setHours(BUSINESS_END_HOUR, 0, 0, 0);
|
||
|
||
const sliceStart = Math.max(cursor.getTime(), windowStart.getTime(), start.getTime());
|
||
const sliceEnd = Math.min(end.getTime(), windowEnd.getTime());
|
||
if (sliceEnd > sliceStart) {
|
||
total += sliceEnd - sliceStart;
|
||
}
|
||
}
|
||
cursor.setDate(cursor.getDate() + 1);
|
||
cursor.setHours(0, 0, 0, 0);
|
||
}
|
||
|
||
return total;
|
||
}
|
||
|
||
export function effectiveElapsedMs(
|
||
track: { startTime: Date | string; metadata?: Record<string, unknown> | null },
|
||
nowMs: number
|
||
): number {
|
||
const meta = track.metadata || {};
|
||
const start = new Date(track.startTime).getTime();
|
||
const pausedAt = meta.pausedAt ? new Date(String(meta.pausedAt)).getTime() : null;
|
||
const effectiveEnd = pausedAt ? Math.min(nowMs, pausedAt) : nowMs;
|
||
|
||
let elapsed = isBusinessHoursEnabled()
|
||
? businessMsBetween(new Date(start), new Date(effectiveEnd))
|
||
: effectiveEnd - start;
|
||
|
||
const accumulatedPause = Number(meta.accumulatedPauseMs || 0);
|
||
elapsed = Math.max(0, elapsed - accumulatedPause);
|
||
return elapsed;
|
||
}
|