/** * Transforme une date du format français (DD/MM/YYYY) au format iso (YYYY-MM-DD) * Exemple d'utilisation : * new Date(Date.fromFRA("30/06/2024")) -> new Date("2024-06-30") * @param {string} dateFra * @returns {string} dateIso */ Date.fromFRA = function (dateFra) { if (dateFra == "") return ""; // Expression régulière pour valider le format de date ISO (YYYY-MM-DD) const regexDateFra = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/; // Vérification du format de la date ISO if (!regexDateFra.test(dateFra)) { throw new Error( `La date (format français) passée en paramètre [${dateFra}] n'est pas valide.` ); } // Conversion du format français (DD/MM/YYYY) au format ISO (YYYY-MM-DD) return `${dateFra.substring(6, 10)}-${dateFra.substring( 3, 5 )}-${dateFra.substring(0, 2)}`; }; /** * Transforme une date du format iso (YYYY-MM-DD) au format français (DD/MM/YYYY) * Exemple d'utilisation : * Date.toFRA("2024-06-30") -> "30/06/2024" * @param {string} dateIso * @returns {string} dateFra */ Date.toFRA = function (dateIso) { if (dateIso == "") return ""; // Expression régulière pour valider le format de date ISO (YYYY-MM-DD) const regexDateIso = /^\d{4}-(0\d|1[0-2])-([0-2]\d|3[01])$/; // Vérification du format de la date ISO if (!regexDateIso.test(dateIso)) { throw new Error( `La date ISO passée en paramètre [${dateIso}] n'est pas valide.` ); } // Conversion du format ISO (YYYY-MM-DD) en format français (DD/MM/YYYY) return `${dateIso.substring(8, 10)}/${dateIso.substring( 5, 7 )}/${dateIso.substring(0, 4)}`; }; /** * Vérifie si le début de l'une des périodes est avant la fin de l'autre * et si la fin de cette période est après le début de l'autre. * @param {Object} period {deb:Object, fin:Object} * @param {Object} interval {deb:Object, fin:Object} * @returns vrai si la periode et l'interval ont une intersection commune */ Date.intersect = function (period, interval) { return period.deb <= interval.fin && period.fin >= interval.deb; }; Date.removeUTC = function (isoString) { const reg = new RegExp(/[+-][\d:]+$/); return isoString.replace(reg, ""); }; Object.defineProperty(Date.prototype, "isValid", { value: function () { return !Number.isNaN(this.getTime()); }, }); Object.defineProperty(Date.prototype, "startOf", { /** * Génère u la date à la plus petite valeur pour la précision donnée. * @param {string} precision - La précision souhaitée (year, month, day, hours, minutes, seconds, milliseconds). * @returns {Date} - Une nouvelle date ajustée. */ value: function (precision) { const newDate = this.clone(); switch (precision) { case "year": newDate.setMonth(0); case "month": newDate.setDate(1); case "day": newDate.setHours(0); case "hours": newDate.setMinutes(0); case "minutes": newDate.setSeconds(0); case "seconds": newDate.setMilliseconds(0); break; case "milliseconds": break; default: throw new Error( `Invalid precision for startOf function [${precision}]` ); } return newDate; }, }); Object.defineProperty(Date.prototype, "endOf", { /** * Ajuste la date à la plus grande valeur pour la précision donnée. * @param {string} precision - La précision souhaitée (year, month, day, hours, minutes, seconds, milliseconds). * @returns {Date} - Une nouvelle date ajustée. */ value: function (precision) { const newDate = this.clone(); switch (precision) { case "year": newDate.setMonth(11); // Décembre est le 11ème mois (0-indexé) case "month": newDate.setDate(0); // Le jour 0 du mois suivant est le dernier jour du mois courant newDate.setMonth(newDate.getMonth() + 1); case "day": newDate.setHours(23); // 23 heures est la dernière heure de la journée case "hours": newDate.setMinutes(59); // 59 minutes est la dernière minute de l'heure case "minutes": newDate.setSeconds(59); // 59 secondes est la dernière seconde de la minute case "seconds": newDate.setMilliseconds(999); // 999 millisecondes est la dernière milliseconde de la seconde break; case "milliseconds": // Rien à faire pour les millisecondes break; default: throw new Error("Invalid precision for endOf function"); } return newDate; }, }); Object.defineProperty(Date.prototype, "isBefore", { /** * Retourne vrai si la date est située avant la date fournie * @param {Date} date * @returns {boolean} */ value: function (date) { return this.valueOf() < date.valueOf(); }, }); Object.defineProperty(Date.prototype, "isAfter", { /** * Retourne vrai si la date est située après la date fournie * @param {Date} date * @returns {boolean} */ value: function (date) { return this.valueOf() > date.valueOf(); }, }); Object.defineProperty(Date.prototype, "isSame", { /** * Retourne vrai si les dates sont les mêmes * @param {Date} date * @param {string} precision default : "milliseconds" * @returns boolean */ value: function (date, precision = "milliseconds") { return ( this.startOf(precision).valueOf() == date.startOf(precision).valueOf() ); }, }); Object.defineProperty(Date.prototype, "isBetween", { /** * Vérifie si la date est comprise dans une période avec une précision et une inclusivité optionnelles * @param {Date} deb - La date de début de la période * @param {Date} fin - La date de fin de la période * @param {String} bornes - L'inclusivité/exclusivité de la comparaison ("[]", "()", "[)", "(]") * - bornes incluses : [] * - bornes excluses : () * - borne gauche incluse et borne droit excluse : [) * - borne gauche excluse et borne droit incluse : (] */ value: function (deb, fin, bornes = "[]") { // Ajuste la date actuelle, la date de début et la date de fin à la précision spécifiée // Vérifie les bornes en fonction de l'inclusivité/exclusivité spécifiée dans 'bornes' const check_deb = bornes[0] === "(" ? this.valueOf() > deb.valueOf() : this.valueOf() >= deb.valueOf(); const check_fin = bornes[1] === ")" ? fin.valueOf() > this.valueOf() : fin.valueOf() >= this.valueOf(); return check_deb && check_fin; }, }); Object.defineProperty(Date.prototype, "toIsoUtcString", { /** * @returns date au format iso utc (yyyy-mm-ddThh:MM±oo:oo:oo) */ value: function () { // Formater la date et l'heure const date = this; var tzo = -date.getTimezoneOffset(), dif = tzo >= 0 ? "+" : "-", pad = function (num) { return (num < 10 ? "0" : "") + num; }; return ( this.toFakeIso() + dif + pad(Math.floor(Math.abs(tzo) / 60)) + ":" + pad(Math.abs(tzo) % 60) ); }, }); Object.defineProperty(Date.prototype, "toFakeIso", { value: function () { const date = this; pad = function (num) { return (num < 10 ? "0" : "") + num; }; return ( date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + "T" + pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds()) ); }, }); Object.defineProperty(Date.prototype, "clone", { /** * @returns Retourne une copie de la date (copie non liée) */ value: function () { return structuredClone(this); }, }); Object.defineProperty(Date.prototype, "format", { value: function (formatString) { let iso = this.toIsoUtcString(); switch (formatString) { case "DD/MM/Y HH:mm": return this.toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", year: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, timeZone: SCO_TIMEZONE, }); case "DD/MM/YYYY HH:mm": return this.toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", hour12: false, timeZone: SCO_TIMEZONE, }); case "YYYY-MM-DDTHH:mm": // slice : YYYY-MM-DDTHH // slice + 3 : YYYY-MM-DDTHH:mm return iso.slice(0, iso.indexOf(":") + 3); case "YYYY-MM-DD": return iso.slice(0, iso.indexOf("T")); default: return this.toFakeIso(); } }, }); Object.defineProperty(Date.prototype, "add", { /** * Ajoute une valeur spécifiée à un élément de la date. * @param {number} value - La valeur à ajouter. * @param {string} type - Le type de la valeur (year, month, day, hours, minutes, seconds). */ value: function (value, type) { switch (type) { case "years": this.setFullYear(this.getFullYear() + value); break; case "months": this.setMonth(this.getMonth() + value); break; case "days": this.setDate(this.getDate() + value); break; case "hours": this.setHours(this.getHours() + value); break; case "minutes": this.setMinutes(this.getMinutes() + value); break; case "seconds": this.setSeconds(this.getSeconds() + value); break; default: throw new Error( `Invalid type for adding to date | type : ${type} value : ${value}` ); } return this; // Return the modified date }, }); class Duration { /** * Constructeur de la classe Duration. * @param {Date} start - La date de début de la période. * @param {Date} end - La date de fin de la période. */ constructor(start, end) { this.start = start; // Stocke la date de début. this.end = end; // Stocke la date de fin. this.duration = end - start; // Calcule la durée en millisecondes entre les deux dates. } /** * Calcule le nombre d'années entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre d'années arrondi à quatre décimales. */ get years() { const startYear = this.start.getFullYear(); // Obtient l'année de la date de début. const endYear = this.end.getFullYear(); // Obtient l'année de la date de fin. // Calcule la différence en années et arrondit à quatre décimales. return parseFloat((endYear - startYear).toFixed(4)); } /** * Calcule le nombre de mois entre les deux dates, en tenant compte des années et des jours, et arrondit le résultat à quatre décimales. * @return {number} Le nombre de mois arrondi à quatre décimales. */ get months() { const years = this.years; // Nombre d'années complètes. // Calcule la différence en mois, en ajoutant la différence en jours divisée par 30 pour une approximation. const months = years * 12 + (this.end.getMonth() - this.start.getMonth()) + (this.end.getDate() - this.start.getDate()) / 30; // Arrondit à quatre décimales. return parseFloat(months.toFixed(4)); } /** * Calcule le nombre de jours entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre de jours arrondi à quatre décimales. */ get days() { // Convertit la durée en millisecondes en jours et arrondit à quatre décimales. return parseFloat((this.duration / (24 * 60 * 60 * 1000)).toFixed(4)); } /** * Calcule le nombre d'heures entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre d'heures arrondi à quatre décimales. */ get hours() { // Convertit la durée en millisecondes en heures et arrondit à quatre décimales. return parseFloat((this.duration / (60 * 60 * 1000)).toFixed(4)); } /** * Calcule le nombre de minutes entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre de minutes arrondi à quatre décimales. */ get minutes() { // Convertit la durée en millisecondes en minutes et arrondit à quatre décimales. return parseFloat((this.duration / (60 * 1000)).toFixed(4)); } /** * Calcule le nombre de secondes entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre de secondes arrondi à quatre décimales. */ get seconds() { // Convertit la durée en millisecondes en secondes et arrondit à quatre décimales. return parseFloat((this.duration / 1000).toFixed(4)); } /** * Obtient le nombre de millisecondes entre les deux dates et arrondit le résultat à quatre décimales. * @return {number} Le nombre de millisecondes arrondi à quatre décimales. */ get milliseconds() { // Arrondit la durée totale en millisecondes à quatre décimales. return parseFloat(this.duration.toFixed(4)); } } class ScoDocDateTimePicker extends HTMLElement { constructor() { super(); // Définir si le champ est requis this.required = this.hasAttribute("required"); // Initialiser le shadow DOM const shadow = this.attachShadow({ mode: "open" }); // Créer l'input pour la date const dateInput = document.createElement("input"); dateInput.type = "date"; dateInput.id = "date"; // Créer l'input pour l'heure const timeInput = document.createElement("input"); timeInput.type = "time"; timeInput.id = "time"; timeInput.step = 60; // Ajouter les inputs dans le shadow DOM shadow.appendChild(dateInput); shadow.appendChild(timeInput); // Gestionnaires d'événements pour la mise à jour de la valeur dateInput.addEventListener("change", () => this.updateValue()); timeInput.addEventListener("change", () => this.updateValue()); // Style CSS pour les inputs const style = document.createElement("style"); style.textContent = ` input { display: inline-block; } input:invalid { border: 1px solid red; } `; // Ajouter le style au shadow DOM shadow.appendChild(style); //Si une value est donnée let value = this.getAttribute("value"); if (value != null) { this.value = value; } } static get observedAttributes() { return ["show"]; // Ajoute 'show' à la liste des attributs observés } connectedCallback() { // Récupérer l'attribut 'name' this.name = this.getAttribute("name"); // Créer un input caché pour la valeur datetime this.hiddenInput = document.createElement("input"); this.hiddenInput.type = "hidden"; this.hiddenInput.name = this.name; this.appendChild(this.hiddenInput); // Gérer la soumission du formulaire this.closest("form")?.addEventListener("submit", (e) => { if (!this.validate()) { e.preventDefault(); // Empêcher la soumission si non valide this.dispatchEvent( new Event("invalid", { bubbles: true, cancelable: true }) ); } else { // Mettre à jour la valeur de l'input caché avant la soumission this.hiddenInput.value = this.isValid() ? this.valueAsDate.toIsoUtcString() : ""; } }); this.updateDisplay(); } attributeChangedCallback(name, oldValue, newValue) { if (name === "show") { this.updateDisplay(); // Met à jour l'affichage si l'attribut 'show' change } } updateDisplay() { const mode = this.getAttribute("show") || "both"; const dateInput = this.shadowRoot.querySelector("#date"); const timeInput = this.shadowRoot.querySelector("#time"); switch (mode) { case "date": dateInput.style.display = "inline-block"; timeInput.style.display = "none"; break; case "time": dateInput.style.display = "none"; timeInput.style.display = "inline-block"; break; case "both": default: dateInput.style.display = "inline-block"; timeInput.style.display = "inline-block"; } } // Vérifier si la valeur forme une date valide isValid() { return !Number.isNaN(this.valueAsDate.getTime()); } // Valider l'élément validate() { if (this.required && !this.isValid()) { return false; } return true; } // Mettre à jour la valeur interne updateValue() { const dateInput = this.shadowRoot.querySelector("#date"); const timeInput = this.shadowRoot.querySelector("#time"); this._value = `${dateInput.value}T${timeInput.value}`; this.dispatchEvent(new Event("change", { bubbles: true })); // Appliquer le style 'invalid' si nécessaire dateInput.classList.toggle("invalid", this.required && !this.isValid()); timeInput.classList.toggle("invalid", this.required && !this.isValid()); } // Getter pour obtenir la valeur actuelle. get value() { return this._value; } get valueAsObject() { const dateInput = this.shadowRoot.querySelector("#date"); const timeInput = this.shadowRoot.querySelector("#time"); return { date: dateInput.value, time: timeInput.value, }; } // Getter pour obtenir la valeur en tant qu'objet Date. get valueAsDate() { return new Date(this._value); } // Setter pour définir la valeur. Sépare la valeur en date et heure et les définit individuellement. set value(val) { let [date, time] = val.split("T"); this.shadowRoot.querySelector("#date").value = date; time = time.substring(0, 5); this.shadowRoot.querySelector("#time").value = time; this._value = val; } // Setter pour définir la valeur à partir d'un objet avec les propriétés 'date' et 'time'. set valueAsObject(obj) { const dateInput = this.shadowRoot.querySelector("#date"); const timeInput = this.shadowRoot.querySelector("#time"); if (obj.hasOwnProperty("date")) { dateInput.value = obj.date || ""; // Définit la valeur de l'input de date si elle est fournie } if (obj.hasOwnProperty("time")) { timeInput.value = obj.time.substring(0, 5) || ""; // Définit la valeur de l'input d'heure si elle est fournie } // Met à jour la valeur interne en fonction des nouvelles valeurs des inputs this.updateValue(); } // Setter pour définir la valeur à partir d'un objet Date. set valueAsDate(dateVal) { // Formatage de l'objet Date en string et mise à jour de la valeur. this.value = `${dateVal.getFullYear()}-${String( dateVal.getMonth() + 1 ).padStart(2, "0")}-${String(dateVal.getDate()).padStart(2, "0")}T${String( dateVal.getHours() ).padStart(2, "0")}:${String(dateVal.getMinutes()).padStart(2, "0")}`; } } // Définition du nouvel élément personnalisé 'scodoc-datetime'. customElements.define("scodoc-datetime", ScoDocDateTimePicker);