HotXLS vremenske žigove svojstava Excel dokumenta sprema kao UTC unutar datoteke i izlaže ih kao lokalno vrijeme kroz API: TXLSWorkbook.CreatedDate i LastSavedDate za .xls, TXLSXWorkbook.Created i Modified za .xlsx. Od v2.384.48 oba enginea lokalno vrijeme pretvaraju u UTC pri pisanju i natrag pri čitanju, koristeći pravila ljetnog računanja vremena koja vrijede na datumu samog žiga. Do tamo se došlo kroz dva popravka, i obje su greške preživjele iz istog sramotnog razloga: svaki automatizirani round trip prolazio je, dok je Excelov File > Info panel pokazivao krivi dan ili krivi sat. Ako ste pročitali naš pregled o postavljanju svojstava Excel dokumenta u Delphiju, ovo je dio gdje datumi prestaju biti jednostavne vrijednosti
Zašto je test spremanje-ponovno-otvori sakrio grešku od jednog dana?
Round trip u vlastitom krugu sakrio je grešku jer su pisac i čitač dijelili istu krivu konstantu, pa se pogreška poništila sama. Datum u OLE property setu je FILETIME, 64-bitni brojač 100-nanosekundnih otklona od 1601-01-01 UTC ([MS-DTYP] §2.3.3), dok Delphi TDateTime broji dane od 1899-12-30, istog serijskog ishodišta obrađenog u članku o Excel serijskim datumima u Delphiju i sustavima 1900 naspram 1904. Razmak među epohama je 109205 dana, što možete provjeriti i bez kalendara: 25569 (Unix epoha kao TDateTime) plus 109205 daje 134774, Unix epoha izračunata u FILETIME danima. HotXLS buildovi prije v2.384.17 koristili su 109206, pa je svaki žig stvaranja i spremanja zapisivan jedan dan kasno i čitan jedan dan rano. Test suite vidio je vrijednost koju je sam dodijelio; Excel je vidio sutra
const
// dani od FILETIME epohe (1601-01-01) do TDateTime epohe (1899-12-30)
// provjera: 25569 + 109205 = 134774, Unix epoha u FILETIME danima
FileTimeDayBias = 109205;
function UtcDateTimeToFileTimeTicks(UtcStamp: TDateTime): Int64;
begin
// Najprije zaokruži na cijele milisekunde, pa skaliraj na 100 ns otklona.
// Direktno skaliranje Doublea u otklone pretvara 04:00 u 03:59:59.9999
Result := Round((UtcStamp + FileTimeDayBias) * 86400000.0) * 10000;
end;
Komentar o zaokruživanju u toj skici druga je, manja lekcija iz istog koda. Množenje razlomljenog TDateTime izravno s 864.000.000.000 otklona na dan pušta binarnu grešku floating pointa u najniže znamenke, pa je žig točno 04:00 stigao natrag kao 03:59:59.9999. HotXLS v2.384.48 zaokružuje na cijele milisekunde prije skaliranja, pa vrijednosti na puni sat prežive put netaknute. Isto izdanje dodalo je i korak vremenske zone koji ova skica namjerno izostavlja, jer je ulaz ovdje već UTC
Koji SummaryInformation property ID-jevi nose datume?
U property setu \005SummaryInformation definiranom u [MS-OLEPS] vrijeme stvaranja stoji pod property ID-jem $0C (PIDSI_CREATE_DTM), vrijeme posljednjeg spremanja pod $0D (PIDSI_LASTSAVE_DTM), a ukupno vrijeme uređivanja pod $0A (PIDSI_EDITTIME). Stariji HotXLS buildovi zapisivali su žig posljednjeg spremanja u $0E, što je PIDSI_PAGECOUNT, pa Excel nije imao datum spremanja za prikazati nego svojstvo broja stranica s vremenskim žigom. Od v2.384.17 čitač poštuje i taj naslijeđeni raspored: kad $0D nema, a $0E nosi VT_FILETIME, vrijednost se uzima kao vrijeme posljednjeg spremanja. Svaki pročitani PROPVARIANT sada se i oslobađa s PropVariantClear, jer pokvarena datoteka može parkirati string pod bilo kojim od ovih ID-jeva. Ako želite te streamove vidjeti vlastitim očima, uputstvo u članku o čitanju OLE2 compound datoteka u Delphiju bez COM IStoragea pokazuje kako im doći
PIDSI_EDITTIME je zamka unutar zamke. Svojstvo je tipizirano kao VT_FILETIME ali nosi trajanje, sirovi broj proteklih 100 ns otklona bez dodane epohe. Stariji writer tretirao ga je kao datum, dijeleći EditTimeMinutes s 1440 i gurajući rezultat kroz konverziju epohe, pa je 125 minuta uređivanja u datoteku sletjelo kao približno 299 godina. Sadašnji čitač to kodiranje prepoznaje po veličini: nijedna stvarna sesija uređivanja ne obuhvaća tri stoljeća, pa se svakoj vrijednosti od 109206 dana ili više oduzima naslijeđeni pomak prije nego se EditTimeMinutes popuni
var
Book: TXLSWorkbook;
begin
Book := TXLSWorkbook.Create;
try
Book.Title := 'Q3 settlement';
// API vrijednosti su lokalno vrijeme; datoteka sprema UTC FILETIME-ove
Book.CreatedDate := EncodeDate(2026, 1, 15) + EncodeTime(9, 30, 0, 0);
Book.LastSavedDate := Now; // HotXLS zapisuje što dodijelite, sam ne utiskuje Now
Book.RevisionNumber := 7;
Book.EditTimeMinutes := 125; // trajanje, spremljeno kao sirovi otkloni
if Book.SaveAs('settlement.xls') <> 1 then
raise Exception.Create('Save failed');
finally
Book.Free;
end;
end;
Zašto su XLSX datumi bili pomaknuti točno za pomak vremenske zone?
XLSX datumi bili su pomaknuti za pomak zone jer su dcterms:created i dcterms:modified u docProps/core.xml W3CDTF vrijednosti označene s Z, što znači UTC u modelu core properties iz ECMA-376 Part 2, a HotXLS je lokalno vrijeme utiskivao s tom Z nalijepljenom. Radna knjiga stvorena u 09:30 na stroju u UTC+8 nosila je 09:30:00Z, a Excel na istom stroju pretvorio ju je u 17:30. Klasični engine imao je identičnu manu u svojim FILETIME vrijednostima, a prilagođena datumska svojstva dodana kroz TXLSXWorkbook.CustomProperties.AddDate (zapisivana kao vt:filetime) dijelila su istu manu. Od v2.384.48 sve tri putanje pretvaraju prije pisanja i pretvaraju natrag pri čitanju kad god žig nosi Z, a od v2.384.59 strana čitanja poštuje i razlomke sekundi i eksplicitne pomake +hh:mm / -hh:mm
U samoj konverziji naivni popravak i podbaci. LocalFileTimeToFileTime primjenjuje pomak koji vrijedi upravo sada, pa siječanjski žig pretvoren u srpnju izađe pomaknut sat vremena u svakoj zoni s ljetnim računanjem vremena. HotXLS umjesto toga zove TzSpecificLocalTimeToSystemTime i SystemTimeToTzSpecificLocalTime, koji standardno ili ljetno vrijeme biraju prema datumu koji se pretvara, a nedefinirana nulta vrijednost prolazi netaknuto da se nikad ne pretvori u datum 1899 pomaknut za nekoliko sati
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');
// Na stroju postavljenom na Central European Time, core.xml sada nosi
// <dcterms:created xsi:type="dcterms:W3CDTF">2026-07-01T07:30:00Z</dcterms:created>
// (UTC+2 u srpnju), dok se ApprovedOn zapisuje kao 16:00Z (UTC+1 u siječnju)
finally
Book.Free;
end;
end;
Što HotXLS ne pretvara pri čitanju vremenskih žigova?
HotXLS W3CDTF čitač pretvara svaki oblik profila označen zonom od v2.384.59, a jedini slučaj koji i dalje ostavlja na miru je vrijeme bez zone. Prije tog izdanja parser uzimao je prvih 19 znakova i pretvarao iz UTC samo kad je 20. znak bio Z, pa je žig s razlomkom sekunde (01:30:00.5Z) ili eksplicitnim pomakom (+08:00) čitan kao lokalno vrijeme bez prilagodbe i završavao pomaknut za pomak zone. Od HotXLS 2.384.59 Created, Modified i prilagođena svojstva s vrijednošću datuma parsiraju razlomke sekundi bilo koje duljine, Z i pomake +hh:mm / -hh:mm, pretvaraju trenutak u UTC pa u lokalno vrijeme, a žig samo s datumom poput 2026-07-01 čitaju kao taj datum. Žig s vremenom a bez oznake zone, koji W3CDTF profil ne dopušta a ECMA-376 Part 2 za njega ne daje pravilo, i dalje se čita kao lokalno vrijeme nepromijenjen, a žig koji se uopće ne parsira vraća se kao nula. Radne knjige koje prođu kroz Excel u redu su; paketi koje proizvedu drugi generatori koji izbace zonu zaslužuju spot provjeru
Datoteke zapisane starijim HotXLS buildovima druga su poštena granica. XLSX žig zapisan prije v2.384.48 bio je lokalno vrijeme u Z kostimu, i ništa u datoteci ga ne razlikuje od ispravnog, pa ga sadašnji čitač pomjera za pomak zone. Klasični FILETIME žigovi iz tih buildova dobivaju isti pomak, a datum stvaranja zapisan prije v2.384.17 dodatno se čita jedan dan kasno, jer se ni višak dana stare konstante ne može otkriti; samo edit-time kodiranje i smještaj u $0E imaju prepoznatljiv potpis. Imajte na umu i to da je API vrijednost lokalna za stroj koji čita, pa će servis koji radi u UTC i desktop u Tokiju za istu datoteku javiti različite CreatedDate vrijednosti, obje ispravne
Kako testirati vremenske žigove dokumenta?
Vremenske žigove dokumenta testirajte prema nečemu što Vaš vlastiti kod nije napisao. Obje ove greške prošle su provjeru spremi-pa-otvori, jer je simetrična greška nevidljiva simetričnom testu. Usporedite s radnom knjigom spremljenom u Excelu, ili tvrdite sirove bajtove i XML tekst nakon spremanja, i pokrenite suite na stroju postavljenom na ne-UTC zonu s testnim datumom sa svake strane promjene ljetnog vremena. Build agent koji radi u UTC s oduševljenjem će prolaziti stari, pokvareni kod
Vremenski žigovi dokumenta mali su, ali po njima sortiraju evidencijski sustavi, search indeksi i revizijski tragovi, a datum pomaknut za dan ili osam sati gori je od onog koji nedostaje jer ga nitko ne preispituje. HotXLS Delphi spreadsheet komponenta obavlja aritmetiku epoha, property ID-jeve i UTC konverziju za .xls i .xlsx, pa Vaš kod može dodjeljivati obične lokalne TDateTime vrijednosti i prepustiti format datoteke biblioteci