Ouvrez une feuille de calcul, cliquez sur une cellule qui affiche 2026-06-19, et la barre de formule montre encore une date. Lisez la même cellule depuis Delphi et vous obtenez le nombre 46192. Les deux vues sont correctes, car Excel n'a jamais rangé de date dans cette cellule. Il y a rangé un numéro de série, un décompte de jours, et lui a attaché un format de nombre qui indique à l'écran de rendre ce décompte comme une date de calendrier. Il n'existe pas de type date dans la valeur de la cellule. Il y a un nombre et une règle d'affichage, et cette règle d'affichage est la seule chose qui distingue une date d'une quantité ordinaire
Cette séparation est la racine de chaque bogue de date qu'une bibliothèque tableur doit esquiver. Un numéro de série seul ne dit pas quel jour il désigne, parce qu'il ne dit pas quel était le jour zéro. Le même nombre désigne deux dates séparées de quatre ans selon un unique indicateur du classeur. Et un nombre censé être relu comme une date sera relu comme une simple quantité tant que rien n'inspecte son format pour y reconnaître un motif de date. Voilà comment le modèle de date de HotXLS est construit, et pourquoi il doit l'être ainsi
Une cellule de date est un nombre plus un format
Excel range une date sous forme du nombre de jours écoulés depuis une époque, l'heure de la journée occupant la partie fractionnaire. Midi sur un numéro de série porte .5. La partie entière est le décompte de jours. Rien dans la valeur stockée ne la marque comme temporelle. Ce qui la marque, c'est le format de nombre de la cellule : ECMA-376 appelle cela un numFmt, et une cellule dont le code de format décrit un motif de date ou d'heure est affichée comme une date. Retirez le format et la même cellule affiche un nombre ; la valeur sous-jacente, elle, n'a jamais changé
C'est pourquoi la lecture de la valeur d'une cellule vous donne un Variant qui peut être un varDate ou un simple Double, et pourquoi le format de nombre de cette même cellule est le signal qui décide de ce qu'un tiers a voulu dire. Quand HotXLS ouvre un fichier XLSX, une cellule transporte à la fois sa Value et son NumberFormatIndex dans TXLSXCell, et l'index de format est ce que vous consultez pour savoir si le nombre est une date
var
Book: TXLSXWorkbook;
Cell: TXLSXCell;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('timesheet.xlsx') <> 1 then
raise Exception.Create('Cannot open workbook');
Cell := Book.Sheets[0].Cells[1, 1]; // ligne 1, colonne 1 (base 1)
// Value peut arriver comme varDate ou comme simple numéro de série ;
// l'index de format est le signal qui permet de les distinguer.
Writeln('raw value : ', VarToStr(Cell.Value));
Writeln('numFmt idx: ', Cell.NumberFormatIndex);
Writeln('format : ', Cell.NumberFormat);
finally
Book.Free;
end;
end;
Deux époques, distantes de 1462 jours
Le système de dates par défaut, celui qu'utilise tout classeur Windows, compte depuis la toute fin de 1899, de sorte que le numéro de série 1 tombe le premier jour de 1900. L'autre système remonte aux premiers Macintosh et compte depuis le début de 1904, si bien que son numéro de série 1 arrive quatre ans et un jour plus tard. Un classeur consigne le système qu'il utilise dans un seul indicateur. Dans un paquet OOXML, cet indicateur est date1904 sur la partie classeur ; HotXLS l'expose comme la propriété Date1904 du classeur
L'écart entre les deux époques vaut exactement 1462 jours. Ce sont quatre années civiles, trois de 365 jours et une de 366, soit 1461, plus un jour supplémentaire pour le décalage d'un peu plus d'une journée entre les deux conventions de jour zéro. Ce nombre est fixe et vous pouvez le garder en tête. Son importance tient au fait qu'il n'est pas nul. Un numéro de série copié d'un classeur 1904 puis interprété selon les règles de 1900, ou l'inverse, place chaque date à 1462 jours de son emplacement réel, ce qui se présente comme des dates fausses d'un peu plus de quatre ans et se confond facilement avec des données corrompues
Comme le TDateTime propre à Delphi est ancré sur la convention 1900, une bibliothèque qui associe les numéros de série Excel à des TDateTime doit décaler de 1462 dans les deux sens dès que le classeur est marqué 1904. À la lecture d'un numéro de série 1904, soustrayez 1462 avant de le traiter comme un TDateTime ; à l'écriture d'un TDateTime dans un classeur 1904, soustrayez 1462 du numéro de série pour qu'Excel affiche le jour voulu. HotXLS applique ce décalage en interne lorsqu'il sérialise des valeurs de date pour un classeur dont Date1904 est activé, si bien que la valeur affectée sous forme de TDateTime revient au même jour de calendrier à l'écran
La bizarrerie délibérée de 1900 sur les années bissextiles
Le système 1900 comporte une ride célèbre. Excel considère 1900 comme une année bissextile et accepte le 29 février 1900 comme une vraie date, numéro de série 60. L'année 1900 n'était pas bissextile, car les années séculaires ne le sont que si elles sont divisibles par 400, et 1900 ne l'est pas. Ce jour fantôme est un comportement de compatibilité délibéré, hérité d'un ancien tableur livré avec ce défaut, conservé depuis pour que l'arithmétique des numéros de série reste identique à travers des décennies de fichiers
La conséquence pratique est modeste mais réelle : pour toute date à partir du 1er mars 1900, le numéro de série vaut une unité de plus qu'un décompte de jours rigoureusement correct, parce que le 29 février inexistant a consommé un nombre. Une bibliothèque tableur reproduit cette bizarrerie au lieu de la corriger, car reproduire exactement l'arithmétique d'Excel est tout le métier. La corriger décalerait chaque date moderne d'un jour par rapport à ce qu'Excel montre, un résultat pire qu'un écart d'une unité vieux de quarante mille jours auquel aucune date réelle du monde des affaires ne touche jamais. Le système 1904 n'a pas de jour fantôme équivalent, ce qui explique en partie pourquoi quelques entreprises l'ont historiquement préféré
Détecter une date à partir du numFmt
Quand un nombre provient d'un fichier écrit par quelqu'un d'autre, son format est la seule preuve qu'il s'agit d'une date. ECMA-376 attribue un bloc d'identifiants de format intégrés dont le sens est fixé par la spécification, et les formats de date et d'heure occupent des plages connues. Les identifiants 14 à 22 sont les formats de date et d'heure de la locale générale, les familiers m/d/yyyy, h:mm et leurs proches. Les identifiants 45 à 47 sont les formats de temps écoulé. Deux autres bandes, 27 à 36 et 50 à 58, sont les formats de date et d'heure propres à une locale, utilisés pour les calendriers CJK et définis en ECMA-376 18.8.30. Une cellule dont l'identifiant de format de nombre tombe dans l'une de ces plages est une cellule de date ou d'heure
Les identifiants intégrés couvrent les cas courants mais pas les cas personnalisés. Lorsqu'un classeur définit son propre code de format, disons un ordre non standard ou un nom de mois localisé, l'identifiant se situe au-dessus de la plage intégrée et pointe dans la table des formats de nombre du classeur. Pour ceux-là, reconnaître une date suppose de lire la chaîne du code de format et d'y chercher des jetons de date. HotXLS réunit les deux vérifications dans un seul prédicat interne, XlsxNumFmtIsDate, qui renvoie vrai immédiatement pour les plages de dates intégrées et analyse sinon le code de format personnalisé via XlsxFormatCodeIsDate. Le versant public en est la chaîne NumberFormat de la cellule et son NumberFormatIndex, qui vous donnent à la fois le code de format résolu et l'identifiant à tester
Pourquoi l'analyseur de format ne peut pas se contenter de chercher d et m
Analyser un code de format à la recherche de jetons de date paraît trivial jusqu'à ce que vous vous rappeliez ce qui vit d'autre dans un format de nombre. Une recherche naïve des lettres qui écrivent les dates, les d, m, y, h et s du jour, du mois, de l'année, de l'heure et de la seconde, se trompera sur deux structures qui ne sont pas du tout des jetons de date
La première est le littéral de chaîne entre guillemets. Un format de nombre peut incorporer du texte littéral entre guillemets doubles, si bien qu'un format financier comme #,##0 "MM" ajoute les caractères M et M à un nombre sans la moindre signification temporelle. Un analyseur qui compterait les lettres à l'intérieur des guillemets comme des jetons de mois signalerait à tort ce format monétaire comme une date. La seconde est la section entre crochets. Les formats de nombre portent des directives entre crochets : des noms de couleur comme [Red], des conditions de comparaison comme [>1000], des étiquettes de locale, et les marqueurs de temps écoulé [h] et [mm]. Une partie du contenu entre crochets contient des lettres de date et une autre non, et traiter le texte entre crochets comme le corps du format mène à la fois à des faux positifs et à des cas manqués
L'analyseur correct parcourt le code de format caractère par caractère, en suivant s'il se trouve à l'intérieur d'un littéral entre guillemets et à quelle profondeur il est imbriqué dans les crochets, et il respecte aussi l'échappement par barre oblique inverse qui protège le caractère suivant. Seule une lettre de date non échappée trouvée hors de tout littéral de chaîne et hors de toute section entre crochets compte comme un vrai jeton de date. C'est exactement ainsi que XlsxFormatCodeIsDate analyse : un guillemet bascule un état interne au littéral qui suspend la détection de jetons jusqu'au guillemet fermant, une barre oblique inverse saute le caractère suivant, et un compteur de profondeur de crochets suspend la détection à l'intérieur des passages [...]. Le bénéfice, c'est que #,##0 "MM" est correctement lu comme un format de nombre, alors qu'un code personnalisé laconique ne contenant rien d'autre qu'un unique m ou d hors guillemets reste correctement reconnu comme une date
Lire les dates issues de fichiers tiers
Tout ce qui précède converge vers un seul flux de travail : retransformer un nombre écrit par une autre application en une date digne de confiance. Le numéro de série vous donne le décompte de jours, l'indicateur Date1904 du classeur vous dit depuis quelle époque ce décompte est mesuré, et l'identifiant de format de nombre de la cellule ou son code personnalisé est la seule preuve que le nombre était bien censé être une date au départ. Laissez tomber l'un des trois et vous obtenez une réponse fausse mais plausible plutôt qu'une erreur visible
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Cell: TXLSXCell;
r: Integer;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('vendor-export.xlsx') <> 1 then
raise Exception.Create('Cannot open export');
// L'indicateur 1904 vaut pour tout le classeur : lisez-le une fois,
// appliquez-le à chaque numéro de série que le classeur renvoie.
if Book.Date1904 then
Writeln('workbook uses the 1904 date system')
else
Writeln('workbook uses the 1900 date system');
Sheet := Book.Sheets[0];
for r := 1 to 10 do
begin
Cell := Sheet.Cells[r, 1];
// Une date n'est une date que si son format le dit ; la même valeur
// numérique avec un format ordinaire est juste une quantité.
Writeln(Format('row %d value=%s numFmt=%d code="%s"',
[r, VarToStr(Cell.Value), Cell.NumberFormatIndex, Cell.NumberFormat]));
end;
finally
Book.Free;
end;
end;
Le versant BIFF hérité comporte un piège supplémentaire qui mérite d'être nommé. Dans un flux .xls ancien, une suite de cellules numériques adjacentes peut être empaquetée dans un unique enregistrement multicellule, le MULRK, qui range plusieurs valeurs avec leurs références de format dans une seule structure. Les cellules de date rangées ainsi n'en sont pas moins des dates parce qu'elles sont empaquetées, donc le même test d'identifiant de format doit atteindre l'intérieur de l'enregistrement multicellule et s'appliquer cellule par cellule, et le décalage 1904 gouverne toujours chaque numéro de série qu'il livre. Un lecteur qui n'inspecte que les enregistrements de nombre autonomes, et saute les empaquetés, transformera silencieusement une colonne de dates en colonne d'entiers
Associer les numéros de série à TDateTime en pratique
Une fois que la vérification de format confirme une date et que l'indicateur Date1904 est connu, la conversion est mécanique. Une valeur que HotXLS renvoie déjà comme varDate est un TDateTime utilisable directement. Une valeur qui arrive sous forme de simple Double, ce qui se produit quand la source a écrit un numéro de série sans format de date reconnu, se convertit en la lisant comme un décompte de jours sur l'axe 1900 puis, pour un classeur 1904, en soustrayant d'abord le décalage de 1462 jours pour aligner les époques. Dans l'autre sens, affecter un TDateTime à une cellule range le numéro de série basé sur 1900, et HotXLS applique le même décalage de 1462 jours à la sauvegarde quand le classeur est marqué 1904, si bien que le fichier enregistré montre la date voulue plutôt qu'une date dérivant de quatre ans
Positionnez l'indicateur délibérément quand vous générez un classeur. Par défaut, Date1904 reste faux, ce qui correspond à Excel pour Windows et convient presque toujours ; ne le passez à vrai que si vous reproduisez un classeur venu du Mac ou si un système en aval attend précisément l'axe 1904. La règle unique qui évite toute cette classe d'erreurs de quatre ans est la cohérence : choisissez l'époque une fois par classeur, écrivez chaque date selon elle, et relisez chaque numéro de série selon l'indicateur que le fichier porte réellement
Les dates ne sont qu'une colonne dans une histoire plus large sur ce que contient vraiment une cellule. La couche de métadonnées voisine, le titre, l'auteur et les horodatages qui accompagnent la grille, est traitée dans notre article sur les métadonnées de classeur et les propriétés de document, où les mêmes valeurs Created et Modified sont rangées comme TDateTime avec la même convention non renseigné égale zéro. Quand une date résulte d'un calcul plutôt que d'une valeur stockée, les règles d'évaluation exposées dans notre article sur le moteur de formules et les fonctions personnalisées déterminent le numéro de série que le format rendra ensuite. Les deux reposent sur le même modèle de date livré dans le HotXLS Delphi Component pour Delphi et C++Builder, qui lit et écrit les dates XLS et XLSX sans automation Excel