Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
11 changed files with 179 additions and 102 deletions
Showing only changes of commit 3a963cb1b4 - Show all commits

View File

@ -36,10 +36,10 @@
.infos { .infos {
position: relative; position: relative;
width: fit-content;
display: flex; display: flex;
justify-content: space-evenly; justify-content: start;
align-content: center; align-content: center;
gap: 10px;
} }
#datestr { #datestr {
@ -48,7 +48,7 @@
border: 1px #444 solid; border: 1px #444 solid;
border-radius: 5px; border-radius: 5px;
padding: 5px; padding: 5px;
min-width: 100px; min-width: 250px;
display: inline-block; display: inline-block;
min-height: 20px; min-height: 20px;
} }

View File

@ -112,6 +112,7 @@ function validateSelectors(btn) {
getAssiduitesFromEtuds(true); getAssiduitesFromEtuds(true);
document.querySelector(".selectors").disabled = true; document.querySelector(".selectors").disabled = true;
$("#tl_date").datepicker("option", "disabled", true);
generateMassAssiduites(); generateMassAssiduites();
generateAllEtudRow(); generateAllEtudRow();
btn.remove(); btn.remove();
@ -173,6 +174,7 @@ function uniqueCheckBox(box) {
* @param {CallableFunction} errors fonction à effectuer en cas d'échec * @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/ */
function sync_get(path, success, errors) { function sync_get(path, success, errors) {
//TODO Optimiser : rendre asynchrone + sans jquery
console.log("sync_get " + path); console.log("sync_get " + path);
$.ajax({ $.ajax({
async: false, async: false,
@ -188,16 +190,21 @@ function sync_get(path, success, errors) {
* @param {CallableFunction} success fonction à effectuer en cas de succès * @param {CallableFunction} success fonction à effectuer en cas de succès
* @param {CallableFunction} errors fonction à effectuer en cas d'échec * @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/ */
function async_get(path, success, errors) { async function async_get(path, success, errors) {
console.log("async_get " + path); console.log("async_get " + path);
$.ajax({ try {
async: true, const response = await fetch(path);
type: "GET", if (response.ok) {
url: path, const data = await response.json();
success: success, success(data);
error: errors, } else {
}); throw new Error("Network response was not ok.");
} }
} catch (error) {
errors(error);
}
}
/** /**
* Fait une requête POST de façon synchrone * Fait une requête POST de façon synchrone
* @param {String} path adresse distante * @param {String} path adresse distante
@ -206,6 +213,7 @@ function async_get(path, success, errors) {
* @param {CallableFunction} errors fonction à effectuer en cas d'échec * @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/ */
function sync_post(path, data, success, errors) { function sync_post(path, data, success, errors) {
//TODO Optimiser : rendre asynchrone + sans jquery
console.log("sync_post " + path); console.log("sync_post " + path);
$.ajax({ $.ajax({
async: false, async: false,
@ -223,17 +231,28 @@ function sync_post(path, data, success, errors) {
* @param {CallableFunction} success fonction à effectuer en cas de succès * @param {CallableFunction} success fonction à effectuer en cas de succès
* @param {CallableFunction} errors fonction à effectuer en cas d'échec * @param {CallableFunction} errors fonction à effectuer en cas d'échec
*/ */
function async_post(path, data, success, errors) { async function async_post(path, data, success, errors) {
console.log("sync_post " + path); console.log("async_post " + path);
return $.ajax({ try {
async: true, const response = await fetch(path, {
type: "POST", method: "POST",
url: path, headers: {
data: JSON.stringify(data), "Content-Type": "application/json",
success: success, },
error: errors, body: JSON.stringify(data),
}); });
if (response.ok) {
const responseData = await response.json();
success(responseData);
} else {
throw new Error("Network response was not ok.");
} }
} catch (error) {
errors(error);
}
}
// <<== Gestion des actions de masse ==>> // <<== Gestion des actions de masse ==>>
const massActionQueue = new Map(); const massActionQueue = new Map();
@ -583,10 +602,13 @@ function formatDate(date, styles = { dateStyle: "full" }) {
*/ */
function updateDate() { function updateDate() {
const dateInput = document.querySelector("#tl_date"); const dateInput = document.querySelector("#tl_date");
let date = $(dateInput).datepicker("getDate");
if (date == null) {
date = new Date(Date.fromFRA(dateInput.value));
}
const date = dateInput.valueAsDate ?? new Date();
let dateStr = ""; let dateStr = "";
if (!verifyNonWorkDays(date.getDay(), nonWorkDays)) { if (!verifyNonWorkDays(date.getDay(), nonWorkDays)) {
dateStr = formatDate(date).capitalize(); dateStr = formatDate(date).capitalize();
} else { } else {
@ -606,7 +628,11 @@ function updateDate() {
) )
); );
openAlertModal("Attention", div, "", "#eec660"); openAlertModal("Attention", div, "", "#eec660");
dateInput.value = lastWorkDay.toISOString().split("T")[0]; $(dateInput).datepicker(
"setDate",
Date.toFRA(lastWorkDay.toIsoUtcString().split("T")[0])
);
dateStr = formatDate(lastWorkDay).capitalize(); dateStr = formatDate(lastWorkDay).capitalize();
} }
document.querySelector("#datestr").textContent = dateStr; document.querySelector("#datestr").textContent = dateStr;
@ -639,15 +665,15 @@ function setupDate(onchange = null) {
const input = document.querySelector("#tl_date"); const input = document.querySelector("#tl_date");
datestr.addEventListener("click", () => { datestr.addEventListener("click", () => {
if (!input.disabled) { if (!document.querySelector(".selectors").disabled) {
try { try {
input.showPicker(); document.querySelector(".infos .ui-datepicker-trigger").click();
} catch {} } catch {}
} }
}); });
if (onchange != null) { if (onchange != null) {
input.addEventListener("change", onchange); $(input).change(onchange);
} }
} }
@ -726,7 +752,7 @@ function getTimeLineTimes() {
//getPeriodValues() -> retourne la position de la timeline [a,b] avec a et b des number //getPeriodValues() -> retourne la position de la timeline [a,b] avec a et b des number
let values = getPeriodValues(); let values = getPeriodValues();
//On récupère la date //On récupère la date
const dateiso = document.querySelector("#tl_date").value; const dateiso = getDate().format("YYYY-MM-DD");
//On génère des objets temps //On génère des objets temps
values = values.map((el) => { values = values.map((el) => {
@ -757,9 +783,10 @@ function isConflictSameAsPeriod(conflict, period = undefined) {
* @returns {Date} la date sélectionnée * @returns {Date} la date sélectionnée
*/ */
function getDate() { function getDate() {
const date = new Date(document.querySelector("#tl_date").value); const date =
date.setHours(0, 0, 0, 0); $("#tl_date").datepicker("getDate") ??
return date; new Date(Date.fromFRA(document.querySelector("#tl_date").value));
return date.startOf("day");
} }
/** /**
@ -768,10 +795,7 @@ function getDate() {
*/ */
function getNextDate() { function getNextDate() {
const date = getDate(); const date = getDate();
const next = new Date(date.valueOf()); return date.clone().add(1, "days");
next.setDate(date.getDate() + 1);
next.setHours(0, 0, 0, 0);
return next;
} }
/** /**
* Retourne un objet date représentant le jour précédent * Retourne un objet date représentant le jour précédent
@ -779,10 +803,7 @@ function getNextDate() {
*/ */
function getPrevDate() { function getPrevDate() {
const date = getDate(); const date = getDate();
const next = new Date(date.valueOf()); return date.clone().add(-1, "days");
next.setDate(date.getDate() - 1);
next.setHours(0, 0, 0, 0);
return next;
} }
/** /**
@ -790,31 +811,6 @@ function getPrevDate() {
* @param {Date} date * @param {Date} date
* @returns {string} la date iso avec le timezone * @returns {string} la date iso avec le timezone
*/ */
function toIsoString(date) {
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)
);
}
/** /**
* Transforme un temps numérique en une date * Transforme un temps numérique en une date
@ -823,7 +819,7 @@ function toIsoString(date) {
*/ */
function numberTimeToDate(nb) { function numberTimeToDate(nb) {
time = toTime(nb).replace("h", ":"); time = toTime(nb).replace("h", ":");
date = document.querySelector("#tl_date").value; date = getDate().format("YYYY-MM-DD");
datetime = `${date}T${time}`; datetime = `${date}T${time}`;
@ -843,8 +839,8 @@ function numberTimeToDate(nb) {
function getAssiduitesFromEtuds(clear, deb, fin) { function getAssiduitesFromEtuds(clear, deb, fin) {
const etudIds = Object.keys(etuds).join(","); const etudIds = Object.keys(etuds).join(",");
const date_debut = deb ? deb : toIsoString(getPrevDate()); const date_debut = deb ? deb : getPrevDate().toIsoUtcString();
const date_fin = fin ? fin : toIsoString(getNextDate()); const date_fin = fin ? fin : getNextDate().toIsoUtcString();
if (clear) { if (clear) {
assiduites = {}; assiduites = {};
@ -1137,8 +1133,8 @@ function getAssiduiteValue(field) {
* @param {String | Number} etudid identifiant de l'étudiant * @param {String | Number} etudid identifiant de l'étudiant
*/ */
function actualizeEtudAssiduite(etudid) { function actualizeEtudAssiduite(etudid) {
const date_debut = toIsoString(getPrevDate()); const date_debut = getPrevDate().toIsoUtcString();
const date_fin = toIsoString(getNextDate()); const date_fin = getNextDate().toIsoUtcString();
const url_api = const url_api =
getUrl() + getUrl() +

View File

@ -1,3 +1,53 @@
/**
* 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)}`;
};
Object.defineProperty(Date.prototype, "isValid", { Object.defineProperty(Date.prototype, "isValid", {
value: function () { value: function () {
return !Number.isNaN(this.getTime()); return !Number.isNaN(this.getTime());
@ -175,6 +225,7 @@ Object.defineProperty(Date.prototype, "clone", {
Object.defineProperty(Date.prototype, "format", { Object.defineProperty(Date.prototype, "format", {
value: function (formatString) { value: function (formatString) {
let iso = this.toIsoUtcString();
switch (formatString) { switch (formatString) {
case "DD/MM/Y HH:mm": case "DD/MM/Y HH:mm":
return this.toLocaleString("fr-FR", { return this.toLocaleString("fr-FR", {
@ -196,10 +247,11 @@ Object.defineProperty(Date.prototype, "format", {
}); });
case "YYYY-MM-DDTHH:mm": case "YYYY-MM-DDTHH:mm":
let iso = this.toIsoUtcString();
// slice : YYYY-MM-DDTHH // slice : YYYY-MM-DDTHH
// slice + 3 : YYYY-MM-DDTHH:mm // slice + 3 : YYYY-MM-DDTHH:mm
return iso.slice(0, iso.indexOf(":") + 3); return iso.slice(0, iso.indexOf(":") + 3);
case "YYYY-MM-DD":
return iso.slice(0, iso.indexOf("T"));
default: default:
return this.toIsoUtcString(); return this.toIsoUtcString();
} }

View File

@ -12,10 +12,10 @@
<!-- Statistiques d'assiduité (nb pres, nb retard, nb absence) + nb justifié --> <!-- Statistiques d'assiduité (nb pres, nb retard, nb absence) + nb justifié -->
<h4>Statistiques d'assiduité</h4> <h4>Statistiques d'assiduité</h4>
<div class="stats-inputs"> <div class="stats-inputs">
<label class="stats-label"> Date de début<input type="date" name="stats_date_debut" id="stats_date_debut" <label class="stats-label"> Date de début<input type="text" class="datepicker" name="stats_date_debut"
value="{{date_debut}}"></label> id="stats_date_debut" value="{{date_debut}}"></label>
<label class="stats-label"> Date de fin<input type="date" name="stats_date_fin" id="stats_date_fin" <label class="stats-label"> Date de fin<input type="text" class="datepicker" name="stats_date_fin"
value="{{date_fin}}"></label> id="stats_date_fin" value="{{date_fin}}"></label>
<button onclick="stats()">Actualiser</button> <button onclick="stats()">Actualiser</button>
</div> </div>
@ -87,20 +87,20 @@
function stats() { function stats() {
const dd_val = document.getElementById('stats_date_debut').value; const dd_val = document.getElementById('stats_date_debut').value;
const df_val = document.getElementById('stats_date_fin').value; const df_val = document.getElementById('stats_date_fin').value;
let date_debut = new Date(Date.fromFRA(dd_val));
if (dd_val == "" || df_val == "") { let date_fin = new Date(Date.fromFRA(df_val));
if (dd_val == "" || df_val == "" || !date_debut.isValid() || !date_debut.isValid()) {
openAlertModal("Dates invalides", document.createTextNode('Les dates sélectionnées sont invalides')); openAlertModal("Dates invalides", document.createTextNode('Les dates sélectionnées sont invalides'));
return; return;
} }
const date_debut = new Date(dd_val + "T00:00"); date_debut = date_debut.startOf("day")
const date_fin = new Date(df_val + "T23:59"); date_fin = date_fin.endOf("day")
if (date_debut.valueOf() > date_fin.valueOf()) { if (date_debut.isAfter(date_fin)) {
openAlertModal("Dates invalides", document.createTextNode('La date de début se situe après la date de fin.')); openAlertModal("Dates invalides", document.createTextNode('La date de début se situe après la date de fin.'));
return; return;
} }
countAssiduites(date_debut.toIsoUtcString(), date_fin.toIsoUtcString()) countAssiduites(date_debut.toIsoUtcString(), date_fin.toIsoUtcString())
} }

View File

@ -9,7 +9,7 @@
<div class="infos"> <div class="infos">
Date: <span id="datestr"></span> Date: <span id="datestr"></span>
<input type="date" name="tl_date" id="tl_date" value="{{ date }}"> <input type="text" class="datepicker" name="tl_date" id="tl_date" value="{{ date }}">
</div> </div>
{{timeline|safe}} {{timeline|safe}}

View File

@ -20,7 +20,8 @@
<div class="infos-button">Groupes&nbsp;: {{grp|safe}}</div> <div class="infos-button">Groupes&nbsp;: {{grp|safe}}</div>
<div class="infos-button" style="margin-left: 24px;">Date&nbsp;: <span style="margin-left: 8px;" <div class="infos-button" style="margin-left: 24px;">Date&nbsp;: <span style="margin-left: 8px;"
id="datestr"></span> id="datestr"></span>
<input type="date" name="tl_date" id="tl_date" value="{{ date }}" onchange="updateDate()"> <input type="text" class="datepicker" name="tl_date" id="tl_date" value="{{ date }}"
onchange="updateDate()">
</div> </div>
</div> </div>
</fieldset> </fieldset>

View File

@ -10,10 +10,10 @@
<h2>Visualisation de l'assiduité {{gr_tit|safe}}</h2> <h2>Visualisation de l'assiduité {{gr_tit|safe}}</h2>
<div class="stats-inputs"> <div class="stats-inputs">
<label class="stats-label"> Date de début <input type="date" name="stats_date_debut" id="stats_date_debut" <label class="stats-label"> Date de début <input type="text" class="datepicker" name="stats_date_debut"
value="{{date_debut}}"></label> id="stats_date_debut" value="{{date_debut}}"></label>
<label class="stats-label"> Date de fin <input type="date" name="stats_date_fin" id="stats_date_fin" <label class="stats-label"> Date de fin <input type="text" class="datepicker" name="stats_date_fin"
value="{{date_fin}}"></label> id="stats_date_fin" value="{{date_fin}}"></label>
<button onclick="stats()">Changer</button> <button onclick="stats()">Changer</button>
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a> <a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>

View File

@ -258,7 +258,7 @@
const success = () => { const success = () => {
const separatorTime = document.getElementById("promptTime").value; const separatorTime = document.getElementById("promptTime").value;
const dateString = const dateString =
document.querySelector("#tl_date").value + `T${separatorTime}`; getDate().format("YYYY-MM-DD") + `T${separatorTime}`;
const separtorDate = new Date(dateString); const separtorDate = new Date(dateString);
const assiduite_debut = new Date(this.selectedAssiduite.date_debut); const assiduite_debut = new Date(this.selectedAssiduite.date_debut);

View File

@ -13,7 +13,8 @@
*/ */
function createMiniTimeline(assiduitesArray, day = null) { function createMiniTimeline(assiduitesArray, day = null) {
const array = [...assiduitesArray]; const array = [...assiduitesArray];
const dateiso = day == null ? document.getElementById("tl_date").value : day;
const dateiso = day == null ? getDate().format("YYYY-MM-DD") : day;
const timeline = document.createElement("div"); const timeline = document.createElement("div");
timeline.className = "mini-timeline"; timeline.className = "mini-timeline";
if (isSingleEtud()) { if (isSingleEtud()) {

View File

@ -93,9 +93,10 @@
function updateSelect(moduleimpl_id, query = "#moduleimpl_select", dateIso = null) { function updateSelect(moduleimpl_id, query = "#moduleimpl_select", dateIso = null) {
let sem = getEtudFormSemestres() let sem = getEtudFormSemestres()
if (dateIso == null) { if (!dateIso) {
dateIso = document.querySelector("#tl_date").value dateIso = getDate().format("YYYY-MM-DD")
} }
sem = filterFormSemestres(sem, dateIso) sem = filterFormSemestres(sem, dateIso)
const mod = getModulesImplByFormsemestre(sem) const mod = getModulesImplByFormsemestre(sem)
populateSelect(mod, moduleimpl_id, query); populateSelect(mod, moduleimpl_id, query);

View File

@ -25,6 +25,7 @@
############################################################################## ##############################################################################
import datetime import datetime
import re
from flask import g, request, render_template, flash from flask import g, request, render_template, flash
from flask import abort, url_for, redirect from flask import abort, url_for, redirect
@ -319,7 +320,7 @@ def signal_assiduites_etud():
render_template( render_template(
"assiduites/pages/signal_assiduites_etud.j2", "assiduites/pages/signal_assiduites_etud.j2",
sco=ScoData(etud), sco=ScoData(etud),
date=date, date=_dateiso_to_datefr(date),
morning=morning, morning=morning,
lunch=lunch, lunch=lunch,
timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])), timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
@ -425,8 +426,8 @@ def bilan_etud():
) )
# Gestion des dates du bilan (par défaut l'année scolaire) # Gestion des dates du bilan (par défaut l'année scolaire)
date_debut: str = f"{scu.annee_scolaire()}-09-01" date_debut: str = f"01/09/{scu.annee_scolaire()}"
date_fin: str = f"{scu.annee_scolaire()+1}-06-30" date_fin: str = f"30/06/{scu.annee_scolaire()+1}"
# Récupération de la métrique d'assiduité # Récupération de la métrique d'assiduité
assi_metric = scu.translate_assiduites_metric( assi_metric = scu.translate_assiduites_metric(
@ -697,7 +698,7 @@ def signal_assiduites_group():
"assiduites/pages/signal_assiduites_group.j2", "assiduites/pages/signal_assiduites_group.j2",
gr_tit=gr_tit, gr_tit=gr_tit,
sem=sem["titre_num"], sem=sem["titre_num"],
date=date, date=_dateiso_to_datefr(date),
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
grp=sco_groups_view.menu_groups_choice(groups_infos), grp=sco_groups_view.menu_groups_choice(groups_infos),
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id), moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
@ -843,7 +844,7 @@ def visu_assiduites_group():
"assiduites/pages/signal_assiduites_group.j2", "assiduites/pages/signal_assiduites_group.j2",
gr_tit=gr_tit, gr_tit=gr_tit,
sem=sem["titre_num"], sem=sem["titre_num"],
date=date, date=_dateiso_to_datefr(date),
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
grp=sco_groups_view.menu_groups_choice(groups_infos), grp=sco_groups_view.menu_groups_choice(groups_infos),
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id), moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
@ -1015,8 +1016,8 @@ def visu_assi_group():
inverse=False, inverse=False,
short=False, short=False,
), ),
date_debut=dates["debut"], date_debut=_dateiso_to_datefr(dates["debut"]),
date_fin=dates["fin"], date_fin=_dateiso_to_datefr(dates["debut"]),
gr_tit=gr_tit, gr_tit=gr_tit,
group_ids=request.args.get("group_ids", None), group_ids=request.args.get("group_ids", None),
sco=ScoData(formsemestre=groups_infos.get_formsemestre()), sco=ScoData(formsemestre=groups_infos.get_formsemestre()),
@ -1318,6 +1319,31 @@ def test():
# --- Fonctions internes --- # --- Fonctions internes ---
def _dateiso_to_datefr(date_iso: str) -> str:
"""
_dateiso_to_datefr Transforme une date iso en date format français
Args:
date_iso (str): date au format iso (YYYY-MM-DD)
Raises:
ValueError: Si l'argument `date_iso` n'est pas au bon format
Returns:
str: date au format français (DD/MM/YYYY)
"""
regex_date_iso: str = r"^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])$"
# Vérification de la date_iso
if not re.match(regex_date_iso, date_iso):
raise ValueError(
f"La dateiso passée en paramètre [{date_iso}] n'est pas valide."
)
return f"{date_iso[8:10]}/{date_iso[5:7]}/{date_iso[0:4]}"
def _get_date_str(deb: datetime.datetime, fin: datetime.datetime) -> str: def _get_date_str(deb: datetime.datetime, fin: datetime.datetime) -> str:
""" """
_get_date_str transforme une période en chaîne lisible _get_date_str transforme une période en chaîne lisible