v_1.5_patch_0.2
This commit is contained in:
+115
@@ -0,0 +1,115 @@
|
||||
'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,
|
||||
};
|
||||
Reference in New Issue
Block a user