Files
Infosec/lib/dates.js
T

116 lines
4.2 KiB
JavaScript
Raw Normal View History

2026-09-13 20:09:20 +01:00
'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,
};