'use strict'; /** * Dates used by folders. * * "Recorded Date" is chosen by the administrator when the folder is created. * "Legal Validity" is always derived from it: recorded date + 30 days. It is * never stored, so it can never drift out of step with the recorded date. */ const VALIDITY_DAYS = 30; const DATE_RE = /^\d{4}-\d{2}-\d{2}$/; /** True when the value is a real calendar date in YYYY-MM-DD form. */ function isValidDate(value) { if (!DATE_RE.test(String(value || ''))) return false; const [y, m, d] = String(value).split('-').map(Number); const dt = new Date(Date.UTC(y, m - 1, d)); return dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d; } /** Recorded date + 30 days, as YYYY-MM-DD. Returns null for a missing date. */ function legalValidity(recordedDate) { if (!isValidDate(recordedDate)) return null; const [y, m, d] = String(recordedDate).split('-').map(Number); const dt = new Date(Date.UTC(y, m - 1, d)); dt.setUTCDate(dt.getUTCDate() + VALIDITY_DAYS); return dt.toISOString().slice(0, 10); } /** Attach the derived validity date to a folder row. */ function withValidity(folder) { return { ...folder, legal_validity: legalValidity(folder.recorded_date) }; } // --------------------------------------------------------------------------- // Display formatting. // Dates are STORED as ISO (YYYY-MM-DD / YYYY-MM-DD HH:MM:SS) so they sort and // compare correctly, and PRESENTED as DD-MM-YYYY. // --------------------------------------------------------------------------- /** ISO date (or datetime) -> "DD-MM-YYYY". Returns '' for anything unusable. */ function toDisplayDate(value) { if (!value) return ''; const m = String(value).match(/^(\d{4})-(\d{2})-(\d{2})/); if (!m) return String(value); return `${m[3]}-${m[2]}-${m[1]}`; } /** ISO datetime -> "DD-MM-YYYY HH:MM:SS" (or DD-MM-YYYY when no time part). */ function toDisplayDateTime(value) { if (!value) return ''; const str = String(value).replace('T', ' ').replace('Z', ''); const m = str.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ ](\d{2}:\d{2})(:\d{2})?)?/); if (!m) return str; const date = `${m[3]}-${m[2]}-${m[1]}`; return m[4] ? `${date} ${m[4]}${m[5] || ''}` : date; } /** * Accept a date typed as DD-MM-YYYY (what users see) or YYYY-MM-DD (what the * browser's native date input sends) and return ISO, or '' when unusable. */ function parseInputDate(value) { const v = String(value || '').trim(); if (!v) return ''; let iso = ''; const dmy = v.match(/^(\d{2})-(\d{2})-(\d{4})$/); if (dmy) iso = `${dmy[3]}-${dmy[2]}-${dmy[1]}`; else if (DATE_RE.test(v)) iso = v; else return ''; return isValidDate(iso) ? iso : ''; } // --------------------------------------------------------------------------- // Access validity windows, chosen by an administrator when approving. // --------------------------------------------------------------------------- const DURATIONS = { '24h': { label: 'Valid for 24 hours', ms: 24 * 60 * 60 * 1000 }, '15d': { label: 'Valid for 15 days', ms: 15 * 24 * 60 * 60 * 1000 }, '30d': { label: 'Valid for 30 days', ms: 30 * 24 * 60 * 60 * 1000 }, forever: { label: 'Valid forever', ms: null }, }; const DURATION_KEYS = Object.keys(DURATIONS); /** Current UTC time as "YYYY-MM-DD HH:MM:SS". */ function nowIso() { return new Date().toISOString().replace('T', ' ').slice(0, 19); } /** * Expiry timestamp for a duration, measured from the moment of approval. * Returns null for "forever" (no expiry). */ function expiryFor(durationKey, fromDate) { const d = DURATIONS[durationKey]; if (!d) return undefined; // caller should treat as invalid if (d.ms === null) return null; // forever const base = fromDate ? new Date(fromDate) : new Date(); return new Date(base.getTime() + d.ms).toISOString().replace('T', ' ').slice(0, 19); } /** True when an expiry timestamp is in the past. */ function isExpired(expiresAt) { if (!expiresAt) return false; // null = forever return new Date(String(expiresAt).replace(' ', 'T') + 'Z').getTime() <= Date.now(); } module.exports = { VALIDITY_DAYS, DATE_RE, isValidDate, legalValidity, withValidity, toDisplayDate, toDisplayDateTime, parseInputDate, DURATIONS, DURATION_KEYS, nowIso, expiryFor, isExpired, };