forked from ScoDoc/ScoDoc
596 lines
18 KiB
JavaScript
596 lines
18 KiB
JavaScript
/**
|
|
* 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);
|
|
}
|
|
|
|
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);
|