Skip to content

perf(calendar_event): calcul des occurrences limité à la plage demandée - #65

Open
AntTBD wants to merge 3 commits into
jeedom:betafrom
AntTBD:beta
Open

perf(calendar_event): calcul des occurrences limité à la plage demandée#65
AntTBD wants to merge 3 commits into
jeedom:betafrom
AntTBD:beta

Conversation

@AntTBD

@AntTBD AntTBD commented Aug 17, 2026

Copy link
Copy Markdown

Description

calculOccurrence() itérait depuis la 1ère occurrence de l'évènement, quelle que soit la plage demandée : des milliers de tours de boucle inutiles à chaque rendu de widget pour un agenda ancien.

La boucle démarre maintenant directement au début de la plage, en avançant d'un multiple entier de la fréquence de répétition — la grille des occurrences reste donc inchangée. Limité aux unités de durée fixe (minutes, heures, jours, semaines), l'arithmétique PHP sur les mois n'étant pas associative en fin de mois.

Deux correctifs adjacents, dans des commits séparés :

  • getNationalDay() recevait un argument de trop et l'année courante au lieu de celle de l'occurrence : les options "jours fériés" étaient fausses au-delà de l'année suivante. Résultat mis en cache par année au passage.
  • Plafond d'itérations sur la boucle + isset() manquants (warnings PHP 8).

Comment tester. Sur beta, exporter toutes les occurrences et le temps de calcul :

mkdir -p /var/www/html/tmp && sudo -u www-data php -d memory_limit=512M -r 'require_once "/var/www/html/core/php/core.inc.php"; $s=(new DateTime("-6 months"))->format("Y-m-d H:i:s"); $f=(new DateTime("+6 months"))->format("Y-m-d H:i:s"); $o=""; $n=0; $b=microtime(true); foreach(calendar_event::all() as $e) foreach($e->calculOccurrence($s,$f) as $c){ $o.=$e->getId()."|".$c["start"]."|".$c["end"]."\n"; $n++; } $ms=(microtime(true)-$b)*1000; file_put_contents("/var/www/html/tmp/occ_avant.txt", $o); printf("AVANT : %d occurrences en %.1f ms\n", $n, $ms);'

Puis avec cette PR appliquée, la même commande vers occ_apres.txt :

mkdir -p /var/www/html/tmp && sudo -u www-data php -d memory_limit=512M -r 'require_once "/var/www/html/core/php/core.inc.php"; $s=(new DateTime("-6 months"))->format("Y-m-d H:i:s"); $f=(new DateTime("+6 months"))->format("Y-m-d H:i:s"); $o=""; $n=0; $b=microtime(true); foreach(calendar_event::all() as $e) foreach($e->calculOccurrence($s,$f) as $c){ $o.=$e->getId()."|".$c["start"]."|".$c["end"]."\n"; $n++; } $ms=(microtime(true)-$b)*1000; file_put_contents("/var/www/html/tmp/occ_apres.txt", $o); printf("APRES : %d occurrences en %.1f ms\n", $n, $ms);'

Comparer :

diff /var/www/html/tmp/occ_avant.txt /var/www/html/tmp/occ_apres.txt && echo "IDENTIQUE"

IDENTIQUE attendu, hors évènements "jours fériés" au-delà de l'année suivante (2ème commit). Le gain est d'autant plus net que la plage est courte : sur ±7 jours, celle du rendu de widget, remplacer -6 months/+6 months par -7 days/+7 days.

Suggested changelog entry

EN: Occurrences are now calculated only over the requested date range
FR: Les occurrences sont désormais calculées uniquement sur la plage de dates demandée

Related issues/external references

Fait suite à #53, réécrite sur la version actuelle de beta.

Types of changes

  • Bug fix (non-breaking change which fixes)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
    • This change is only breaking for integrators, not for external standards or end-users.
  • Documentation improvement

PR checklist

AntTBD added 3 commits August 17, 2026 11:59
…emandée

calculOccurrence() itérait depuis la 1ère occurrence de l'évènement : un agenda quotidien créé en 2017 effectuait ~3000 tours de boucle à chaque rendu de widget, et un agenda horaire plusieurs dizaines de milliers. Les dashboards comportant plusieurs widgets agenda devenaient sensiblement lents.

On avance d'un multiple entier de la fréquence de répétition jusqu'au dernier cycle se terminant avant $_startDate : la grille des occurrences est strictement identique à celle produite par itération. Limité aux unités de durée fixe (minutes, heures, jours, semaines), l'arithmétique PHP sur les mois n'étant pas associative en fin de mois (31 janvier +1 mois +1 mois != 31 janvier +2 mois) ; les mois et les années continuent d'itérer, le nombre de tours y étant déjà faible. Le saut est divisé par deux jusqu'à ne plus ignorer que des occurrences se terminant avant la plage, et abandonné s'il n'y parvient pas.

Sortie également des invariants de la boucle : $excludeDate est indexé par clé pour un test isset() en O(1) au lieu d'un in_array(), et la vérification de until est résolue une seule fois avant la boucle au lieu d'appeler strtotime() à chaque itération.
…ence

getNationalDay() était appelée sous la forme getNationalDay(date('Y'), strtotime($startDate)), avec un second argument que la méthode n'accepte pas et l'année courante au lieu de celle de l'occurrence. Les options « sauf jours fériés » et « seulement les jours fériés » étaient donc fausses pour toute occurrence au-delà de l'année suivante.

On passe l'année de l'occurrence, et le résultat est mis en cache par année : easter_date() et 20 appels à mktime() étaient recalculés à chaque itération.
…s warnings PHP 8

Limitation de la boucle de calcul des occurrences à MAX_OCCURRENCE_LOOP itérations, avec un log de debug, afin qu'un évènement mal configuré ne puisse plus figer le rendu du widget. Ajout des tests isset() manquants sur $repeat['excludeDay'][...] et $repeat['freq'].
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant