Aller au contenu

%DIFF

Écart entre deux dates ou heures

Calcule la différence entre deux dates, deux heures ou deux horodatages, en nombre entier d'années, de mois, de jours, d'heures, de minutes, de secondes ou de microsecondes.

Syntaxe

%DIFF(valeur1 : valeur2 : unité)
valeur1
Le premier terme de la soustraction : une date, une heure ou un horodatage.
valeur2
Ce qu'on retranche. Même type que valeur1, sauf qu'on peut mélanger date et horodatage.
unité
*YEARS (*Y), *MONTHS (*M), *DAYS (*D), *HOURS (*H), *MINUTES (*MN), *SECONDS (*S), *MSECONDS (*MS). Les unités possibles dépendent du type : jours, mois et années pour une date, heures, minutes et secondes pour une heure.
Résultat
Un entier, valeur1 - valeur2 : positif si valeur1 est la plus récente, négatif sinon. Seules les unités entières comptent : le reste est supprimé, sans arrondi.

À quoi ça sert

Dès qu'on veut un retard de paiement, un âge, une ancienneté ou un délai de traitement, il faut soustraire deux dates. %DIFF est la fonction prévue pour cela : elle soustrait deux valeurs et rend l'écart dans l'unité choisie.

Exemple 1 : Jours, mois, années

**free
// %DIFF : ecart entre deux dates, en jours, en mois et en annees.
ctl-opt dftactgrp(*no) actgrp(*new);

dcl-s facture  date inz(d'2026-09-30');
dcl-s echeance date inz(d'2026-12-25');
dcl-s naissance date inz(d'2000-05-15');
dcl-s aujourd  date inz(d'2026-09-30');
dcl-s ecart    int(10);

// La date la plus recente en premier : le resultat est positif.
ecart = %diff(echeance : facture : *days);
snd-msg 'Jours avant echeance : [' + %char(ecart) + ']';

// Dans l'autre sens, le resultat est negatif.
ecart = %diff(facture : echeance : *days);
snd-msg 'Ordre inverse        : [' + %char(ecart) + ']';

ecart = %diff(echeance : facture : *months);
snd-msg 'Mois entiers         : [' + %char(ecart) + ']';

// L'age : nombre d'annees entieres ecoulees.
ecart = %diff(aujourd : naissance : *years);
snd-msg 'Age                  : [' + %char(ecart) + ']';

// Une condition sur l'ecart : facture en retard de plus de 30 jours ?
if %diff(aujourd : d'2026-08-15' : *days) > 30;
  snd-msg 'Facture du 15/08 : relance necessaire';
endif;

*inlr = *on;

Résultat réel, compilé et exécuté sur IBM i 7.5 :

Jours avant echeance : [86]
Ordre inverse        : [-86]
Mois entiers         : [2]
Age                  : [26]
Facture du 15/08 : relance necessaire

Du 30 septembre au 25 décembre : 86 jours, ou 2 mois entiers. En inversant les deux dates, le résultat est négatif. L'âge est simplement l'écart en années entre la naissance et le jour du contrôle. Le résultat se teste directement dans un if.

Exemple 2 : Le résultat est tronqué

**free
// %DIFF : le resultat est toujours un nombre entier, la partie fractionnaire est perdue.
ctl-opt dftactgrp(*no) actgrp(*new);

dcl-s ecart int(10);
dcl-s debut timestamp;
dcl-s fin   timestamp;

// Fin de mois : du 31 janvier au 28 fevrier, un mois n'est pas ecoule...
ecart = %diff(d'2026-02-28' : d'2026-01-31' : *months);
snd-msg '31/01 -> 28/02 : ' + %char(ecart) + ' mois';
// ... mais il l'est au 1er mars.
ecart = %diff(d'2026-03-01' : d'2026-01-31' : *months);
snd-msg '31/01 -> 01/03 : ' + %char(ecart) + ' mois';

// L'age change le jour de l'anniversaire, pas avant.
ecart = %diff(d'2026-05-14' : d'2000-05-15' : *years);
snd-msg 'Veille anniv.   : ' + %char(ecart) + ' ans';
ecart = %diff(d'2026-05-15' : d'2000-05-15' : *years);
snd-msg 'Jour anniv.     : ' + %char(ecart) + ' ans';

// 23 h 59 min 59 s d'ecart : zero jour.
debut = z'2026-09-30-08.00.00.000000';
fin   = z'2026-10-01-07.59.59.000000';
ecart = %diff(fin : debut : *days);
snd-msg 'Presque 1 jour  : ' + %char(ecart) + ' jour';

// Vers le passe, la troncature va aussi vers zero (pas vers -infini).
fin = z'2026-09-29-08.00.01.000000';
ecart = %diff(fin : debut : *days);
snd-msg 'Passe, jours    : ' + %char(ecart);
ecart = %diff(fin : debut : *hours);
snd-msg 'Passe, heures   : ' + %char(ecart);

*inlr = *on;

Résultat réel, compilé et exécuté sur IBM i 7.5 :

31/01 -> 28/02 : 0 mois
31/01 -> 01/03 : 1 mois
Veille anniv.   : 25 ans
Jour anniv.     : 26 ans
Presque 1 jour  : 0 jour
Passe, jours    : 0
Passe, heures   : -23

Du 31 janvier au 28 février, il y a 0 mois ; jusqu'au 1er mars, 1 mois. L'âge passe de 25 à 26 ans le jour de l'anniversaire, pas avant. 23 h 59 min 59 s font 0 jour. Vers le passé, on tronque aussi vers zéro : 23 h 59 min 59 s en arrière donnent -23 heures et 0 jour.

Exemple 3 : Heures, horodatages et microsecondes

**free
// %DIFF sur des heures et des horodatages : secondes, microsecondes, depassement.
ctl-opt dftactgrp(*no) actgrp(*new);

dcl-s ouverture time inz(t'08.00.00');
dcl-s fermeture time inz(t'17.45.30');
dcl-s debut     timestamp inz(z'2026-09-30-08.00.00.000000');
dcl-s fin       timestamp inz(z'2026-10-02-08.00.00.500000');
dcl-s petit     int(10);
dcl-s grand     int(20);

// Sur des heures : heures, minutes et secondes.
snd-msg 'Ouvert (min) : ' + %char(%diff(fermeture : ouverture : *minutes));
snd-msg 'Ouvert (s)   : ' + %char(%diff(fermeture : ouverture : *seconds));
snd-msg 'Ouvert (h)   : ' + %char(%diff(fermeture : ouverture : *hours));

// Sur des horodatages : *SECONDS ignore la demi-seconde.
snd-msg 'Ecart (s)    : ' + %char(%diff(fin : debut : *seconds));

// *MSECONDS compte en MICROSECONDES : 2 jours + 0,5 s = 172 800 500 000.
grand = %diff(fin : debut : *mseconds);
snd-msg 'Ecart (ms)   : ' + %char(grand);

// Ce nombre ne tient pas dans un entier de 10 chiffres.
monitor;
  petit = %diff(fin : debut : *mseconds);
  snd-msg 'Dans int(10) : ' + %char(petit);
on-error;
  snd-msg 'Dans int(10) : depassement, statut ' + %char(%status);
endmon;

*inlr = *on;

Résultat réel, compilé et exécuté sur IBM i 7.5 :

Ouvert (min) : 585
Ouvert (s)   : 35130
Ouvert (h)   : 9
Ecart (s)    : 172800
Ecart (ms)   : 172800500000
Dans int(10) : depassement, statut 103

Sur des heures, on choisit heures, minutes ou secondes. Avec *MSECONDS, le résultat est en microsecondes : 172 800 500 000 pour deux jours et une demi-seconde. Il dépasse la capacité d'un int(10) : le statut 103 signale le débordement, d'où la zone int(20).

Le piège

Le résultat est un entier tronqué, pas arrondi. Du 31 janvier au 28 février, %DIFF(... : *MONTHS) renvoie 0 alors que 31 janvier + %MONTHS(1) donne justement le 28 février : les deux fonctions ne sont pas exactement inverses en fin de mois. De même, une personne de 25 ans et 364 jours a encore 25 ans.

Autres surprises vérifiées : *MSECONDS compte en microsecondes (et fait déborder un int(10)), et l'ordre des dates change le signe du résultat. Enfin %DIFF(horodatage : date : *HOURS) est refusé à la compilation (RNF0593) : les heures ne s'appliquent pas à une date.