diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 6faad0fbd..bb26b58ad 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -53,7 +53,6 @@ import requests from pytz import timezone -import dateutil.parser as dtparser import flask from flask import g, request, Response @@ -230,7 +229,7 @@ def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or No """ try: - date: datetime.datetime = dtparser.isoparse(date) + date: datetime.datetime = datetime.datetime.fromisoformat(date) return date if convert else True except (dtparser.ParserError, ValueError, TypeError): return None if convert else False diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index b4b646f73..0c8edc610 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -51,360 +51,6 @@ Object.defineProperty(Array.prototype, "reversed", { enumerable: false, }); -// <= Gestion des dates => - -Object.defineProperty(Date.prototype, "toPrecision", { - /** - * Ajuste la date à la précision donnée. - * @param {String} precision - La précision désirée (année, mois, jour, etc.) - */ - value: function (precision) { - const newDate = new Date(this.getTime()); - - // Trouve l'indice de la précision spécifiée dans le tableau des précisions - const precisionsIndex = - precision != undefined - ? DatePrecisions.indexOf(precision) - : DatePrecisions.length - 1; - - // Réinitialise toutes les parties de la date moins significatives que la précision spécifiée - for (let i = precisionsIndex + 1; i < DatePrecisions.length; i++) { - const p = DatePrecisions[i]; - switch (p) { - case "month": - // Les mois en JavaScript sont indexés à partir de 0, donc on met 0 pour janvier - newDate.setMonth(0); - break; - case "day": - // Les jours en JavaScript commencent à 1, donc on met 1 pour le premier jour du mois - newDate.setDate(1); - break; - case "hour": - newDate.setHours(0); - break; - case "minute": - newDate.setMinutes(0); - break; - case "second": - newDate.setSeconds(0); - break; - case "millisecond": - newDate.setMilliseconds(0); - break; - } - } - - return newDate; - }, -}); - -Object.defineProperty(Date.prototype, "isBefore", { - value: function (date) { - return this.valueOf() < date.valueOf(); - }, -}); -Object.defineProperty(Date.prototype, "isAfter", { - 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 - * @returns boolean - */ - value: function (date) { - return this.valueOf() == date.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} precision - La précision pour la comparaison (année, mois, jour, etc.) - * @param {String} bornes - L'inclusivité/exclusivité de la comparaison ("[]", "()", "[)", "(]") - */ - value: function (deb, fin, precision, bornes = "[]") { - // Ajuste la date actuelle, la date de début et la date de fin à la précision spécifiée - const thisPrecision = this.toPrecision(precision); - const debPrecision = deb.toPrecision(precision); - const finPrecision = fin.toPrecision(precision); - - // Vérifie les bornes en fonction de l'inclusivité/exclusivité spécifiée dans 'bornes' - const check_deb = - bornes[0] === "(" - ? thisPrecision > debPrecision - : thisPrecision >= debPrecision; - const check_fin = - bornes[1] === ")" - ? finPrecision > thisPrecision - : finPrecision >= thisPrecision; - - 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 () { - const date = this; - var tzo = -date.getTimezoneOffset(), - dif = tzo >= 0 ? "+" : "-", - 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()) + - dif + - pad(Math.floor(Math.abs(tzo) / 60)) + - ":" + - pad(Math.abs(tzo) % 60) - ); - }, -}); - -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) { - 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, - }); - 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, - }); - - case "YYYY-MM-DDTHH:mm": - let iso = this.toIsoUtcString(); - // slice : YYYY-MM-DDTHH - // slice + 3 : YYYY-MM-DDTHH:mm - return iso.slice(0, iso.indexOf(":") + 3); - default: - return this.toIsoUtcString(); - } - }, -}); - -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 - }, -}); - -Object.defineProperty(Date.prototype, "startOf", { - /** - * Ajuste 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"); - } - 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; - }, -}); - -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)); - } -} - /** * Ajout des évents sur les boutons d'assiduité * @param {Document | HTMLFieldSetElement} parent par défaut le document, un field sinon diff --git a/app/static/js/date_utils.js b/app/static/js/date_utils.js new file mode 100644 index 000000000..4c24624af --- /dev/null +++ b/app/static/js/date_utils.js @@ -0,0 +1,397 @@ +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 () { + const date = this; + var tzo = -date.getTimezoneOffset(), + dif = tzo >= 0 ? "+" : "-", + 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()) + + dif + + pad(Math.floor(Math.abs(tzo) / 60)) + + ":" + + pad(Math.abs(tzo) % 60) + ); + }, +}); + +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) { + 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, + }); + 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, + }); + + case "YYYY-MM-DDTHH:mm": + let iso = this.toIsoUtcString(); + // slice : YYYY-MM-DDTHH + // slice + 3 : YYYY-MM-DDTHH:mm + return iso.slice(0, iso.indexOf(":") + 3); + default: + return this.toIsoUtcString(); + } + }, +}); + +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(); + // Initialisation du shadow DOM pour l'encapsulation du style et du comportement. + const shadow = this.attachShadow({ mode: "open" }); + + // Création de l'input pour la date. + const dateInput = document.createElement("input"); + dateInput.type = "date"; + dateInput.id = "date"; + + // Création de l'input pour l'heure. + const timeInput = document.createElement("input"); + timeInput.type = "time"; + timeInput.id = "time"; + + // Ajout des inputs date et heure dans le shadow DOM. + shadow.appendChild(dateInput); + shadow.appendChild(timeInput); + + // Ajout de gestionnaires d'événements pour mettre à jour la valeur lorsque les inputs changent. + dateInput.addEventListener("change", () => this.updateValue()); + timeInput.addEventListener("change", () => this.updateValue()); + + // Style CSS pour les inputs, ici pour les afficher côte à côte. + const style = document.createElement("style"); + style.textContent = ` + input { + display: inline-block; + } + `; + + // Ajout du style dans le shadow DOM. + shadow.appendChild(style); + } + + // Méthode pour mettre à jour la valeur interne basée sur les inputs de date et d'heure. + updateValue() { + const dateInput = this.shadowRoot.querySelector("#date"); + const timeInput = this.shadowRoot.querySelector("#time"); + // Formatage de la valeur en format datetime ISO (YYYY-MM-DDTHH:MM). + this._value = `${dateInput.value}T${timeInput.value}`; + } + + // Getter pour obtenir la valeur actuelle. + get value() { + return this._value; + } + + // Setter pour définir la valeur. Sépare la valeur en date et heure et les définit individuellement. + set value(val) { + const [date, time] = val.split("T"); + this.shadowRoot.querySelector("#date").value = date; + this.shadowRoot.querySelector("#time").value = time; + this._value = val; + } + + // Getter pour obtenir la valeur en tant qu'objet Date. + get valueAsDate() { + return new Date(this._value); + } + + // 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); diff --git a/app/templates/assiduites/pages/ajout_justificatif.j2 b/app/templates/assiduites/pages/ajout_justificatif.j2 index 83e7366c3..0c102cb13 100644 --- a/app/templates/assiduites/pages/ajout_justificatif.j2 +++ b/app/templates/assiduites/pages/ajout_justificatif.j2 @@ -18,12 +18,12 @@