Ouvrez une feuille de calcul, cliquez sur une cellule qui affiche 2026-06-19, et la barre de formule indique une date. Lisez cette même cellule depuis Delphi et vous obtiendrez le nombre 46192. Ces deux représentations sont correctes, car Excel n'a jamais stocké de date dans cette cellule. Il a stocké un numéro de série (un nombre de jours) et y a associé un format de nombre qui indique à l'écran de restituer ce compte sous forme de date du calendrier. Il n'y a pas de type de date dans la valeur de la cellule. Il y a un nombre et une règle d'affichage, et cette règle est la seule chose qui distingue une date d'une quantité brute.
Cette séparation est à l'origine de tous les bugs de date qu'une bibliothèque de feuille de calcul doit éviter. Un numéro de série seul ne suffit pas à définir le jour, car il n'indique pas quel était le jour zéro. La même valeur désigne deux dates distantes de quatre ans selon un simple indicateur du classeur. Et un nombre qui devrait être interprété comme une date sera lu comme une simple quantité à moins d'inspecter son format et d'identifier un modèle de date. C'est ainsi que le modèle de dates de HotXLS est conçu, et pourquoi il doit l'être.
Deux époques, séparées de 1462 jours
Le système de date par défaut (celui utilisé par chaque classeur Windows) commence le décompte à la toute fin de l'année 1899, de sorte que le numéro de série 1 correspond au premier jour de 1900. L'autre système remonte aux premiers Macintosh et débute au commencement de 1904. Son numéro de série 1 se situe donc quatre ans et un jour plus tard. Un classeur enregistre le système utilisé dans un indicateur (flag). Dans un package OOXML, cet indicateur est date1904 dans la partie classeur ; HotXLS l'expose via la propriété Date1904 du classeur.
L'écart entre les deux époques est précisément de 1462 jours. Cela représente quatre années civiles (trois de 365 jours et une de 366, soit 1461 jours) plus un jour supplémentaire pour l'écart de convention de jour zéro. Ce nombre est fixe. Son importance tient au fait qu'il n'est pas nul. Un numéro de série copié depuis un classeur 1904 et interprété selon les règles 1900 (ou inversement) décale chaque date de 1462 jours, ce qui se traduit par des dates erronées d'un peu plus de quatre ans et peut être confondu avec des données corrompues.
Puisqu'un format TDateTime de Delphi est basé sur la convention 1900, une bibliothèque associant les numéros de série Excel à TDateTime doit appliquer un décalage de 1462 jours dans les deux sens dès lors que le classeur est configuré en 1904. Pour lire une valeur 1904, soustrayez 1462 avant de la traiter comme un TDateTime ; pour écrire un TDateTime dans un classeur 1904, soustrayez 1462 du numéro de série afin qu'Excel affiche le jour attendu. HotXLS effectue ce décalage en interne lorsqu'il sérialise les valeurs de dates pour un classeur dont Date1904 est activé, de sorte que la valeur affectée en tant que TDateTime correspond au même jour du calendrier à l'écran.
L'anomalie délibérée de l'année bissextile 1900
Le système 1900 présente une anomalie historique connue. Excel considère l'année 1900 comme bissextile et accepte le 29 février 1900 comme une date réelle, correspondant au numéro de série 60. L'année 1900 n'était pourtant pas bissextile, car les années de début de siècle ne sont bissextiles que si elles sont divisibles par 400, ce qui n'est pas le cas de 1900. Ce jour fantôme est un comportement de compatibilité délibéré hérité d'un tableur ancien qui comportait ce bug, conservé depuis pour que l'arithmétique des numéros de série reste identique sur des décennies de fichiers.
La conséquence pratique est minime mais réelle : pour toute date égale ou postérieure au 1er mars 1900, le numéro de série est supérieur de un à ce qu'un décompte strictement exact donnerait, car ce 29 février inexistant a consommé une valeur. Une bibliothèque de tableur reproduit cette anomalie plutôt que de la corriger, car reproduire à l'identique les calculs d'Excel est sa fonction première. La corriger décalerait d'un jour chaque date moderne par rapport à l'affichage d'Excel, ce qui s'avère pire que de conserver un décalage de un vieux de quarante mille jours et qu'aucune date actuelle n'atteint. Le système 1904 ne comporte aucun jour fantôme équivalent, ce qui explique pourquoi certains environnements l'ont historiquement privilégié.
Détecter une date à partir de numFmt
Lorsqu'un nombre provient d'un fichier externe, son format est la seule preuve qu'il s'agit d'une date. La norme ECMA-376 spécifie un ensemble d'identifiants de formats intégrés dont la signification est figée, et les formats de date et d'heure occupent des plages connues. Les identifiants 14 à 22 correspondent aux formats généraux de date et d'heure, les habituels m/d/yyyy, h:mm et associés. Les identifiants 45 à 47 sont les formats de temps écoulé. Deux autres plages, de 27 à 36 et de 50 à 58, représentent les formats de date et d'heure locaux utilisés pour les calendriers CJK, définis dans la norme ECMA-376 18.8.30. Une cellule dont l'identifiant de format numérique se situe 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 formats personnalisés. Lorsqu'un classeur définit son propre code de format (par exemple, un ordre non standard ou un nom de mois localisé), l'identifiant se situe au-dessus de la plage intégrée et pointe vers la table de formats numériques du classeur. Pour ces cas, identifier une date consiste à analyser la chaîne du code de format à la recherche d'indicateurs de date. HotXLS regroupe ces deux vérifications dans un prédicat interne unique, XlsxNumFmtIsDate, qui renvoie vrai immédiatement pour les plages de dates intégrées et analyse sinon le code de format personnalisé via XlsxFormatCodeIsDate. L'aspect public de cette opération réside dans la chaîne NumberFormat de la cellule et son NumberFormatIndex, qui vous fournissent à la fois le code de format résolu et l'identifiant à tester.
Pourquoi l'analyseur de format ne peut pas simplement rechercher d et m
Analyser un code de format à la recherche d'indicateurs de date semble trivial, jusqu'à ce que l'on se rappelle des autres éléments présents dans un format numérique. Une recherche simpliste des lettres qui désignent les dates (d, m, y, h et s pour le jour, le mois, l'année, l'heure et la seconde) va échouer sur deux structures qui ne sont pas du tout des indicateurs de date.
La première est la chaîne littérale entre guillemets. Un format de nombre peut intégrer du texte littéral entre guillemets doubles. Ainsi, un format financier tel que #,##0 "MM" ajoute les caractères M et M à un nombre sans aucune signification temporelle. Un analyseur qui comptabilise ces lettres entre guillemets comme des indicateurs de mois marquerait à tort ce format de devise comme une date. Le second élément est la section entre crochets. Les formats de nombres intègrent des directives entre crochets (noms de couleurs comme [Red], conditions de comparaison comme [>1000], étiquettes de paramètres régionaux et marqueurs de temps écoulé [h] et [mm]). Certains contenus entre crochets comportent des lettres de dates et d'autres non, et traiter le texte entre crochets de la même manière que le corps du format conduit à la fois à de faux positifs et à des omissions.
Le correcteur d'analyse parcourt le code de format caractère par caractère, en déterminant s'il se trouve à l'intérieur d'un littéral entre guillemets et à quel niveau d'imbrication de crochets, tout en respectant l'échappement par barre oblique inverse (backslash) qui protège le caractère suivant. Seule une lettre de date non échappée se trouvant hors de tout littéral et de toute section entre crochets est considérée comme un indicateur de date réel. C'est précisément ainsi que procède XlsxFormatCodeIsDate : un guillemet bascule l'analyse en mode littéral (ce qui désactive la détection jusqu'au guillemet fermant), une barre oblique inverse ignore le caractère suivant, et un compteur de profondeur de crochets désactive la détection au sein des séquences [...]. L'avantage est que #,##0 "MM" est correctement interprété comme un format numérique, tandis qu'un code personnalisé condensé ne contenant qu'un simple m ou d hors guillemets reste correctement identifié comme une date.
Lire des dates depuis des fichiers tiers
Tous ces éléments convergent vers un même flux de travail : reconvertir un nombre écrit par une autre application en une date fiable. Le numéro de série fournit le nombre de jours, l'indicateur Date1904 du classeur indique de quelle époque ce compte est issu, et l'identifiant de format ou le code personnalisé de la cellule constitue l'unique preuve que la valeur numérique représentait initialement une date. Omettez l'un de ces trois paramètres et vous obtiendrez un résultat faux cohérent plutôt qu'une erreur explicite.
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');
// The 1904 flag is workbook-wide: read it once, apply it to
// every serial the workbook hands back.
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];
// A date is only a date when its format says so; the same numeric
// value with a plain format is just a quantity.
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 côté BIFF historique a un piège de plus. Dans un flux .xls plus ancien, une séquence de cellules numériques adjacentes peut être empaquetée dans un seul enregistrement multi-cellules, le MULRK, qui stocke plusieurs valeurs avec leurs formats de référence dans une seule structure. Les cellules de dates ainsi stockées restent des dates, de sorte que le test de format doit être fait par cellule et le décalage 1904 continue de régir chaque numéro de série qu'il produit. Un lecteur qui ne vérifierait que les enregistrements de nombres individuels et ignorerait les cellules empaquetées transformerait silencieusement une colonne de dates en une colonne d'entiers.
Passer des numéros de série à TDateTime en pratique
Dès que le format confirme une date et que l'indicateur Date1904 est connu, la conversion est mécanique. Une valeur que HotXLS renvoie comme un varDate est un TDateTime exploitable directement. Une valeur arrivant sous forme de Double brut, ce qui arrive lorsque la source a écrit un numéro de série sans format identifié, se convertit en la lisant comme un compte de jours sur l'axe 1900 et, pour un classeur 1904, en soustrayant le décalage de 1462 jours d'abord pour aligner les époques. Inversement, affecter un TDateTime à une cellule stocke le numéro de série 1900, et HotXLS applique le décalage de 1462 jours à l'enregistrement si Date1904 est actif, afin que le fichier enregistré affiche la date attendue à l'écran.
Définissez l'indicateur de manière délibérée lorsque vous générez un classeur. Par défaut, Date1904 reste faux, ce qui correspond à Excel pour Windows. Activez-le uniquement si vous reproduisez un classeur d'origine Mac ou si le système cible l'exige. La règle unique pour éviter ces erreurs de quatre ans est la cohérence : choisir l'époque une fois par classeur, y écrire chaque date et lire chaque numéro de série selon l'indicateur réellement présent dans le fichier.
Les dates ne sont qu'une colonne parmi d'autres de ce que contient une cellule. La couche de métadonnées associée (titre, auteur et horodatages qui accompagnent la grille) est traitée dans notre article sur les métadonnées de classeurs et les propriétés de documents, où les valeurs Created et Modified sont stockées sous forme de TDateTime avec la même convention de valeur nulle. Lorsqu'une date résulte d'un calcul, les règles d'évaluation présentées dans notre article sur le moteur de calcul et les fonctions personnalisées déterminent le numéro de série que le format applique ensuite. Les deux aspects s'appuient sur le même modèle de dates fourni avec le composant HotXLS pour Delphi et C++Builder, qui lit et écrit les dates XLS et XLSX sans automatisation Excel.
Exemples de code supplémentaires
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');
// The 1904 flag is workbook-wide: read it once, apply it to
// every serial the workbook hands back.
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];
// A date is only a date when its format says so; the same numeric
// value with a plain format is just a quantity.
Writeln(Format('row %d value=%s numFmt=%d code="%s"',
[r, VarToStr(Cell.Value), Cell.NumberFormatIndex, Cell.NumberFormat]));
end;
finally
Book.Free;
end;
end;