116 lines
4.2 KiB
JavaScript
116 lines
4.2 KiB
JavaScript
'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,
|
|
};
|