Å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.
84 lines
2.8 KiB
TypeScript
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
|
|
}
|