HotXLS lagrer Excel-dokumentegenskapenes tidsstempler som UTC i filen og eksponerer dem som lokal tid gjennom API-en: TXLSWorkbook.CreatedDate og LastSavedDate for .xls, TXLSXWorkbook.Created og Modified for .xlsx. Siden v2.384.48 konverterer begge motorer lokal tid til UTC ved skriving og tilbake ved lesing, med reglene for sommertid som gjelder på stempelets egen dato. Veien dit tok to fikser, og begge feilene hadde overlevd av samme pinlige grunn: hver automatiserte rundtur passerte, mens Excels Fil > Info-rute viste feil dag eller feil time. Har du lest oversikten vår om å sette Excel-dokumentegenskaper i Delphi, er dette stedet der datoene slutter å være enkle verdier
Hvorfor skjulte en lagre-og-gjenåpne-test en feil på én dag?
En rundtur med seg selv skjulte feilen fordi writer og leser delte samme gale konstant, så mistaket opphevet seg selv. En dato i et OLE-egenskapssett er en FILETIME, et 64-bits antall 100-nanosekund-tikk siden 1601-01-01 UTC ([MS-DTYP] §2.3.3), mens en Delphi TDateTime teller dager fra 1899-12-30, samme serial-opprinnelse som dekket i Excel-datoserialer i Delphi og 1900- mot 1904-systemene. Avstanden mellom de to epokene er 109205 dager, noe du kan sjekke uten en kalender: 25569 (Unix-epoken som TDateTime) pluss 109205 gir 134774, Unix-epoken regnet i FILETIME-dager. HotXLS-bygg før v2.384.17 brukte 109206, så hvert opprettelses- og lagringsstempel ble skrevet én dag sent og lest én dag tidlig. Testpakken så verdien den selv hadde satt; Excel så i morgen
const
// dager fra FILETIME-epoken (1601-01-01) til TDateTime-epoken (1899-12-30)
// sjekk: 25569 + 109205 = 134774, Unix-epoken i FILETIME-dager
FileTimeDayBias = 109205;
function UtcDateTimeToFileTimeTicks(UtcStamp: TDateTime): Int64;
begin
// Rund til hele millisekunder først, skaler så til 100 ns-tikk.
// Skalerer du Double-en rett til tikk, blir 04:00 til 03:59:59.9999
Result := Round((UtcStamp + FileTimeDayBias) * 86400000.0) * 10000;
end;
Avrundingskommentaren i den skissen er den andre, mindre lærdommen fra samme kode. Ganger du en brøkdelig TDateTime rett opp i 864 000 000 000 tikk per dag, lekker binær flyttallsfeil inn i de nederste sifrene, og et stempel på nøyaktig 04:00 kom tilbake som 03:59:59.9999. HotXLS v2.384.48 runder til hele millisekunder før skalering, så heltime-verdier overlever turen intakt. Samme utgivelse la til tidssonesteget som denne skissen bevisst lar ligge, fordi inngangen her allerede er UTC
Hvilke SummaryInformation-egenskaps-ID-er holder datoene?
I egenskapssettet \005SummaryInformation definert av [MS-OLEPS] bor opprettelsestiden under egenskaps-ID $0C (PIDSI_CREATE_DTM), sist-lagret-tiden under $0D (PIDSI_LASTSAVE_DTM), og total redigeringstid under $0A (PIDSI_EDITTIME). Eldre HotXLS-bygg skrev sist-lagret-stempelet til $0E, som er PIDSI_PAGECOUNT, så Excel hadde ingen lagringsdato å vise og en sideantall-egenskap som holdt et tidsstempel. Siden v2.384.17 ærer leseren også det gamle opplegget: når $0D mangler og $0E bærer en VT_FILETIME, tas verdien som sist-lagret-tid. Hver PROPVARIANT-lesing frigjøres nå også med PropVariantClear, fordi en misdannet fil kan parkere en streng under vilkårlig en av disse ID-ene. Vil du se disse strømmene med egne øyne, viser gjennomgangen om å lese OLE2 compound files i Delphi uten COM IStorage hvordan du når dem
PIDSI_EDITTIME er fellen inni fellen. Egenskapen er typet VT_FILETIME men holder en varighet, det rå antallet forløpte 100 ns-tikk uten noen epoke lagt til. Den gamle writeren behandlet den som en dato, delte EditTimeMinutes på 1440 og presset resultatet gjennom epokekonverteringen, så 125 minutter med redigering landet i filen som omtrent 299 år. Den nåværende leseren kjenner den kodingen igjen på størrelsen: ingen ekte redigeringsøkt spenner over tre århundrer, så enhver verdi på 109206 dager eller mer får det gamle offsetten trukket fra før EditTimeMinutes fylles inn
var
Book: TXLSWorkbook;
begin
Book := TXLSWorkbook.Create;
try
Book.Title := 'Q3 settlement';
// API-verdier er lokal tid; filen lagrer UTC FILETIME-er
Book.CreatedDate := EncodeDate(2026, 1, 15) + EncodeTime(9, 30, 0, 0);
Book.LastSavedDate := Now; // HotXLS skriver det du tildeler, den stempler ikke Now selv
Book.RevisionNumber := 7;
Book.EditTimeMinutes := 125; // en varighet, lagret som rå tikk
if Book.SaveAs('settlement.xls') <> 1 then
raise Exception.Create('Save failed');
finally
Book.Free;
end;
end;
Hvorfor var XLSX-datoer feil med nøyaktig tidssoneforskyvningen?
XLSX-datoer avvek med tidssoneforskyvningen fordi dcterms:created og dcterms:modified i docProps/core.xml er W3CDTF-verdier merket med Z, som betyr UTC under core properties-modellen i ECMA-376 Part 2, og HotXLS stemplet lokal tid med den Z-en hengende på. En arbeidsbok opprettet 09:30 på en maskin i UTC+8 bar 09:30:00Z, og Excel på samme maskin konverterte den til 17:30. Classic-motoren hadde samme feil i FILETIME-verdiene sine, og egendefinerte datoegenskaper lagt til gjennom TXLSXWorkbook.CustomProperties.AddDate (skrevet som vt:filetime) delte den også. Siden v2.384.48 konverterer alle tre veier før skriving og tilbake ved lesing når stempelet bærer en Z, og siden v2.384.59 ærer lesesiden også brøkdesekunder og eksplisitte +hh:mm / -hh:mm-offsetter
Konverteringen i seg selv er der en naiv fiks går galt. LocalFileTimeToFileTime anvender offsetten som gjelder akkurat nå, så et januarstempel konvertert i juli kommer ut en time feil i enhver sone med sommertid. HotXLS kaller TzSpecificLocalTimeToSystemTime og SystemTimeToTzSpecificLocalTime i stedet, som velger standard- eller sommertid fra datoen som konverteres, og en usatt verdi på null passerer urørt, så den aldri blir en 1899-dato forskjøvet noen timer
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('report-template.xlsx') <> 1 then
raise Exception.Create('Template not available');
Book.Created := EncodeDate(2026, 7, 1) + EncodeTime(9, 30, 0, 0);
Book.Modified := Now;
Book.CustomProperties.AddDate('ApprovedOn',
EncodeDate(2026, 1, 20) + EncodeTime(17, 0, 0, 0));
Book.SaveAs('report.xlsx');
// På en maskin satt til Central European Time holder core.xml nå
// <dcterms:created xsi:type="dcterms:W3CDTF">2026-07-01T07:30:00Z</dcterms:created>
// (UTC+2 i juli), mens ApprovedOn skrives som 16:00Z (UTC+1 i januar)
finally
Book.Free;
end;
end;
Hva konverterer ikke HotXLS ved lesing av tidsstempler?
HotXLS W3CDTF-leseren konverterer hver sonemerket form av profilen siden v2.384.59, og det eneste tilfellet den fortsatt lar være, er et klokkeslett uten sone. Før den utgivelsen tok parseren de første 19 tegnene og konverterte fra UTC bare når tegn 20 var Z, så et stempel med brøkdesekunder (01:30:00.5Z) eller en eksplisitt offset (+08:00) ble lest som lokal tid uten justering og endte feil med tidssoneforskyvningen. Siden HotXLS 2.384.59 parser Created, Modified og egendefinerte egenskaper med datoverdi brøkdesekunder av enhver lengde, Z, og +hh:mm / -hh:mm-offsetter, konverterer tidspunktet til UTC og så til lokal tid, og leser et kun-dato-stempel som 2026-07-01 som den datoen. Et stempel med klokkeslett men uten sonemerke, som W3CDTF-profilen ikke tillater og ECMA-376 Part 2 ikke har noen regel for, leses fortsatt som lokal tid uendret, og et stempel som ikke lar seg parse i det hele tatt, kommer tilbake som null. Arbeidsbøker som har passert gjennom Excel er fine; pakker produsert av andre generatorer som slipper sonen, fortjener en stikkprøve
Filer skrevet av eldre HotXLS-bygg er den andre ærlige grensen. Et XLSX-stempel skrevet før v2.384.48 var lokal tid kledd i en Z, og ingenting i filen skiller den fra en korrekt en, så dagens leser forskjøver den med tidssoneforskyvningen. Classic FILETIME-stempler fra de byggene tar samme skift, og en opprettelsesdato skrevet før v2.384.17 leses i tillegg tilbake én dag sent, fordi ekstradagen i den gamle konstanten heller ikke kan oppdages; bare redigeringstidskodingen og $0E-plasseringen har et gjenkjennbart signaturelement. Ha også i minnet at API-verdien er lokal til maskinen som leser, så en tjeneste i UTC og en stasjonær i Tokyo vil rapportere forskjellige CreatedDate-verdier for samme fil, begge korrekte
Hvordan bør du teste dokumenttidsstempler?
Test dokumenttidsstempler mot noe din egen kode ikke skrev. Begge disse feilene passerte en lagre-og-gjenåpne-sjekk, for en symmetrisk feil er usynlig for en symmetrisk test. Sammenlign mot en arbeidsbok lagret av Excel, eller assert de rå bytene og XML-teksten etter lagring, og kjør pakken på en maskin satt til en ikke-UTC-sone med en testdato på hver side av et sommertidsskifte. En build-agent som kjører i UTC, passerer den gamle, ødelagte koden med glede
Dokumenttidsstempler er små, men de er det arkivsystemer, søkeindekser og revisjonsspor sorterer etter, og en dato som er feil med en dag eller åtte timer, er verre enn en manglende, for ingen setter den i tvil. HotXLS Delphi regnearkkomponenten håndterer epoke-regnestykket, egenskaps-ID-ene og UTC-konverteringen for både .xls og .xlsx, så koden din kan tildele vanlige lokale TDateTime-verdier og la filformatet ligge til biblioteket