const fs = require("fs"); const path = require("path"); const YAML = require("yaml"); const { DateTime } = require("luxon"); const HUGO_CONFIG_PATH = path.join(process.cwd(), "config", "_default", "config.yaml"); let cachedTimeZone = null; /** * Récupère le fuseau horaire configuré pour Hugo. * @returns {string} Identifiant IANA du fuseau horaire de Hugo. */ function getHugoTimeZone() { if (cachedTimeZone) { return cachedTimeZone; } const rawConfig = fs.readFileSync(HUGO_CONFIG_PATH, "utf8"); const parsedConfig = YAML.parse(rawConfig); if (!parsedConfig || !parsedConfig.timeZone) { throw new Error("Aucun fuseau horaire Hugo n'a été trouvé dans config/_default/config.yaml."); } cachedTimeZone = String(parsedConfig.timeZone).trim(); if (!cachedTimeZone) { throw new Error("Le fuseau horaire Hugo est vide ou invalide."); } return cachedTimeZone; } /** * Parse une chaîne de date selon les formats Hugo attendus. * @param {string} value Chaîne de date. * @param {string} zone Fuseau horaire IANA. * @param {number} defaultHour Heure par défaut si absente. * @param {number} defaultMinute Minute par défaut si absente. * @returns {import("luxon").DateTime|null} DateTime ou null si invalide. */ function parseHugoDateString(value, zone, defaultHour = 12, defaultMinute = 0) { let trimmed = value.trim(); if (!trimmed) { return null; } if ( (trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith("\"") && trimmed.endsWith("\"")) ) { trimmed = trimmed.slice(1, -1).trim(); } const iso = DateTime.fromISO(trimmed, { setZone: true }); if (iso.isValid) { return iso.setZone(zone); } const formats = [ "yyyy-LL-dd HH:mm:ss", "yyyy-LL-dd'T'HH:mm:ss", "yyyy-LL-dd HH:mm", "yyyy-LL-dd'T'HH:mm", "yyyy-LL-dd", ]; for (const format of formats) { const parsed = DateTime.fromFormat(trimmed, format, { zone }); if (parsed.isValid) { if (format === "yyyy-LL-dd") { return parsed.set({ hour: defaultHour, minute: defaultMinute, second: 0, millisecond: 0 }); } return parsed; } } const rfc2822 = DateTime.fromRFC2822(trimmed, { setZone: true }); if (rfc2822.isValid) { return rfc2822.setZone(zone); } return null; } /** * Convertit une valeur vers un DateTime positionné sur le fuseau horaire Hugo. * @param {Date|import("luxon").DateTime|string|number|null} value Valeur à convertir (null : maintenant). * @returns {import("luxon").DateTime} Instance DateTime alignée sur le fuseau de Hugo. */ function toHugoDateTime(value = null) { const zone = getHugoTimeZone(); if (value === null || value === undefined) { const now = DateTime.now().setZone(zone); if (!now.isValid) { throw new Error(now.invalidReason || "Date actuelle invalide pour le fuseau Hugo."); } return now; } if (DateTime.isDateTime(value)) { const zoned = value.setZone(zone); if (!zoned.isValid) { throw new Error(zoned.invalidReason || "DateTime invalide après application du fuseau Hugo."); } return zoned; } if (value instanceof Date) { const zoned = DateTime.fromJSDate(value, { zone }); if (!zoned.isValid) { throw new Error(zoned.invalidReason || "Date JS invalide pour le fuseau Hugo."); } return zoned; } if (typeof value === "string") { const parsed = parseHugoDateString(value, zone); if (!parsed) { throw new Error(`Chaîne de date invalide : ${value}`); } return parsed; } if (typeof value === "number") { const parsed = DateTime.fromMillis(value, { zone }).setZone(zone); if (!parsed.isValid) { throw new Error(parsed.invalidReason || "Horodatage numérique invalide pour le fuseau Hugo."); } return parsed; } throw new Error("Type de date non pris en charge pour le fuseau horaire Hugo."); } /** * Formate une date selon le format Hugo simple (sans offset). * @param {Date|import("luxon").DateTime|string|number|null} value Valeur à formater. * @returns {string} Date formatée. */ function formatDateTime(value = null) { const zoned = toHugoDateTime(value); const normalized = zoned.set({ millisecond: 0 }); const formatted = normalized.toFormat("yyyy-LL-dd HH:mm:ss"); if (!formatted) { throw new Error("Impossible de formater la date avec le fuseau Hugo."); } return formatted; } /** * Convertit une valeur de frontmatter en DateTime si elle est valide. * @param {import("luxon").DateTime|Date|string|number|null|undefined} value Valeur lue depuis le frontmatter. * @returns {import("luxon").DateTime|null} DateTime utilisable ou null si invalide. */ function parseFrontmatterDate(value) { const zone = getHugoTimeZone(); if (DateTime.isDateTime(value)) { const zoned = value.setZone(zone); return zoned.isValid ? zoned : null; } if (value instanceof Date) { const zoned = DateTime.fromJSDate(value, { zone }); return zoned.isValid ? zoned : null; } if (typeof value === "string") { const parsed = parseHugoDateString(value, zone); return parsed; } if (typeof value === "number" && Number.isFinite(value)) { const zoned = DateTime.fromMillis(value, { zone }).setZone(zone); return zoned.isValid ? zoned : null; } return null; } module.exports = { formatDateTime, getHugoTimeZone, toHugoDateTime, parseFrontmatterDate, };