Files
accounted/extensions/general/cloud-backup/lib/schedule.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

84 lines
2.8 KiB
TypeScript

import type { GoogleDriveSchedule } from '../types'
const STOCKHOLM_TZ = 'Europe/Stockholm'
function isValidHour(hour: unknown): hour is number {
return (
typeof hour === 'number' && Number.isInteger(hour) && hour >= 0 && hour <= 23
)
}
function stockholmDateString(now: Date): string {
// sv-SE with a timeZone yields YYYY-MM-DD.
return new Intl.DateTimeFormat('sv-SE', { timeZone: STOCKHOLM_TZ }).format(now)
}
function stockholmHourOf(date: Date): number {
return Number(
new Intl.DateTimeFormat('sv-SE', {
timeZone: STOCKHOLM_TZ,
hour: '2-digit',
hourCycle: 'h23',
}).format(date)
)
}
/**
* Absolute timestamp of today's HH:00 in Europe/Stockholm. Tries the two
* offsets Sweden uses (CEST +02:00, CET +01:00) and keeps the one that lands
* on the requested wall-clock hour, so DST transitions resolve correctly
* without a timezone library.
*/
export function stockholmSlotForDay(now: Date, hour: number): Date {
const dateStr = stockholmDateString(now)
const hh = String(hour).padStart(2, '0')
for (const offset of ['+02:00', '+01:00']) {
const candidate = new Date(`${dateStr}T${hh}:00:00${offset}`)
if (stockholmHourOf(candidate) === hour) return candidate
}
return new Date(`${dateStr}T${hh}:00:00+01:00`)
}
/** UTC hour that corresponds to a Stockholm wall-clock hour today. */
export function stockholmHourToUtcHour(hour: number, now: Date = new Date()): number {
return stockholmSlotForDay(now, hour).getUTCHours()
}
/**
* Today's scheduled slot as an absolute timestamp, or null when the schedule
* carries no valid hour. `hour_local` (Europe/Stockholm wall-clock, stable
* across DST) wins over the legacy `hour_utc`.
*/
function scheduleSlotForDay(schedule: GoogleDriveSchedule, now: Date): Date | null {
if (isValidHour(schedule.hour_local)) {
return stockholmSlotForDay(now, schedule.hour_local)
}
if (isValidHour(schedule.hour_utc)) {
const slot = new Date(now)
slot.setUTCHours(schedule.hour_utc, 0, 0, 0)
return slot
}
return null
}
/**
* A schedule is due when its daily slot for today has passed and no auto-sync
* attempt has run since that slot. Hour equality is deliberately NOT
* required: if the popular 03:00 batch overruns the cron's time budget, the
* leftover companies stay due and get picked up by the next hourly run
* instead of silently losing the whole day.
*/
export function isScheduleDue(
schedule: GoogleDriveSchedule | null | undefined,
now: Date
): boolean {
if (!schedule?.enabled) return false
const slot = scheduleSlotForDay(schedule, now)
if (!slot) return false
if (now.getTime() < slot.getTime()) return false
if (!schedule.last_auto_sync_at) return true
const lastAttempt = new Date(schedule.last_auto_sync_at).getTime()
return Number.isFinite(lastAttempt) ? lastAttempt < slot.getTime() : true
}