Teknik Makale

Delphi'de Excel Belge Zaman Damgaları: FILETIME, UTC ve DST

HotXLS, Excel belge özelliği zaman damgalarını dosyanın içinde UTC olarak saklar ve API üzerinden yerel saat olarak açar: .xls için TXLSWorkbook.CreatedDate ve LastSavedDate, .xlsx için TXLSXWorkbook.Created ve Modified. v2.384.48'den beri her iki motor da yazarken yerel saati UTC'ye, okurken geri çevirir ve bunu damganın kendi tarihinde geçerli olan yaz saati uygulaması kurallarıyla yapar. Oraya varmak iki düzeltme aldı ve iki hata da aynı utanç verici nedenle hayatta kaldı: her otomatik gidiş dönüş geçti, Excel'in Dosya > Info bölmesiyse yanlış günü ya da yanlış saati gösterdi. Delphi'de Excel belge özelliklerini ayarlama genel bakışımızı okuduysanız, tarihlerin basit değer olmayı bıraktığı yer burasıdır

Kaydet ve yeniden aç testi bir günlük hatayı neden gizledi?

Kendi kendine gidiş dönüş hatayı gizledi, çünkü yazıcı ile okuyucu aynı yanlış sabiti paylaşıyordu ve hata kendi kendini iptal ediyordu. OLE özellik kümesi tarihi bir FILETIME'dır, yani 1601-01-01 UTC'den beri 100 nanosaniyelik tiklerin 64 bitlik sayısı ([MS-DTYP] §2.3.3); Delphi TDateTime ise 1899-12-30'dan itibaren gün sayar — Delphi'de Excel tarih serileri ile 1900 ve 1904 sistemleri makalesinin kapsadığı aynı seri başlangıcı. İki dev arasındaki boşluk 109205 gündür ve bunu takvim olmadan denetleyebilirsiniz: 25569 (TDateTime olarak Unix devri) artı 109205, 134774 eder; yani FILETIME günü sayılan Unix devri. v2.384.17 öncesi HotXLS derlemeleri 109206 kullanıyordu; böylece her oluşturma ve kaydetme damgası bir gün geç yazılıyor, bir gün erken okunuyordu. Test paketi atadığı değeri görüyordu; Excel yarını görüyordu

const
  // FILETIME devri (1601-01-01) ile TDateTime devri (1899-12-30) arasındaki gün sayısı
  // denetim: 25569 + 109205 = 134774, FILETIME günü cinsinden Unix devri
  FileTimeDayBias = 109205;

function UtcDateTimeToFileTimeTicks(UtcStamp: TDateTime): Int64;
begin
  // Önce tam milisaniyeye yuvarla, sonra 100 ns tikine ölçekle.
  // Double'ı doğrudan tike ölçeklemek 04:00'ü 03:59:59.9999 yapar
  Result := Round((UtcStamp + FileTimeDayBias) * 86400000.0) * 10000;
end;
HotXLS zaman çizelgesi: FILETIME devri 1601-01-01, TDateTime devri 1899-12-30 ve 1970 Unix devri; UtcDateTimeToFileTimeTicks'in ardındaki 109205 günlük sapmayı ve v2.384.17 öncesi derlemelerin her CreatedDate damgasını 109206 ile bir gün geç yazıp bir gün erken okuduğunu gösterir
UtcDateTimeToFileTimeTicks taslağı sapmayı, yanlış sabitin kendi kendini iptal ettiği yerde tutar — simetrik bir kaydet-yeniden aç testi atadığı değeri görürken Excel Info bölmesi yarını gösteriyordu

O taslaktaki yuvarlama yorumu aynı koddan çıkan ikinci, daha küçük derstir. Kesirli bir TDateTime'ı doğrudan günde 864.000.000.000 tik ile çarpmak, ikili kayan nokta hatasının alt basamaklara sızmasına izin verir ve tam 04:00 olan bir damga 03:59:59.9999 olarak döner. HotXLS v2.384.48, ölçeklemeden önce tam milisaniyeye yuvarlar; böylece tam saatteki değerler yolculuktan sağlam çıkar. Aynı sürüm, bu taslağın bilinçli olarak dışarıda bıraktığı saat dilimi adımını da ekledi; çünkü buradaki girdi hâlihazırda UTC'dir

Tarihleri hangi SummaryInformation özellik kimlikleri taşır?

[MS-OLEPS]'in tanımladığı \005SummaryInformation özellik kümesinde oluşturma zamanı $0C (PIDSI_CREATE_DTM), son kaydetme zamanı $0D (PIDSI_LASTSAVE_DTM), toplam düzenleme süresi ise $0A (PIDSI_EDITTIME) altında yaşar. Eski HotXLS derlemeleri son kaydetme damgasını PIDSI_PAGECOUNT olan $0E'ye yazıyordu; Excel'in gösterecek kaydetme tarihi yoktu ve bir sayfa sayısı özelliği zaman damgası taşıyordu. v2.384.17'den beri okuyucu o eski yerleşimi de tanır: $0D yokken ve $0E bir VT_FILETIME taşıyorken değer son kaydetme zamanı alınır. Okunan her PROPVARIANT ayrıca artık PropVariantClear ile serbest bırakılır; çünkü bozuk bir dosya bu kimliklerden herhangi birinin altına bir dize park edebilir. Bu akışları kendi gözlerinizle görmek isterseniz, COM IStorage olmadan Delphi'de OLE2 bileşik dosyaları okuma anlatımı onlara nasıl ulaşılacağını gösterir

PIDSI_EDITTIME tuzakların içindeki tuzaktır. Özellik VT_FILETIME olarak yazılmıştır ama bir süre tutar; dev eklenmemiş, geçen 100 ns tiklerinin ham sayısıdır. Eski yazıcı ona tarih gibi davranıyor, EditTimeMinutes'i 1440'a bölüp sonucu dev dönüşümünden itiyordu; böylece 125 dakikalık düzenleme dosyaya kabaca 299 yıl olarak düşüyordu. Güncel okuyucu bu kodlamayı boyutundan tanır: gerçek bir düzenleme oturumu üç yüzyıla yayılmaz, dolayısıyla 109206 gün ve üzeri her değerin eski ofseti, EditTimeMinutes doldurulmadan önce çıkarılır

HotXLS 005SummaryInformation özellik kümesi haritası: $0C'deki PIDSI_CREATE_DTM oluşturma zamanını, $0D'deki PIDSI_LASTSAVE_DTM kaydetme damgasını, $0A'daki PIDSI_EDITTIME bir tarih değil ham süreyi, $0E PIDSI_PAGECOUNT ise eski derlemelerin zaman damgası için yanlış kullandığı yuvayı gösterir
PIDSI_EDITTIME tuzakların içindeki tuzaktır — VT_FILETIME olarak yazılı ama dev eklenmemiş geçen tikleri tutar; boyuta dayalı okuyucu sezgisi gelene dek 125 düzenleme dakikasını kabaca 299 yıla çevirmişti
var
  Book: TXLSWorkbook;
begin
  Book := TXLSWorkbook.Create;
  try
    Book.Title := 'Q3 settlement';
    // API değerleri yerel saattir; dosya UTC FILETIME saklar
    Book.CreatedDate := EncodeDate(2026, 1, 15) + EncodeTime(9, 30, 0, 0);
    Book.LastSavedDate := Now;     // HotXLS atadığınızı yazar, Now damgası basmaz
    Book.RevisionNumber := 7;
    Book.EditTimeMinutes := 125;   // bir süre, ham tik olarak saklanır
    if Book.SaveAs('settlement.xls') <> 1 then
      raise Exception.Create('Save failed');
  finally
    Book.Free;
  end;
end;

XLSX tarihleri neden tam olarak saat dilimi ofseti kadar saptı?

XLSX tarihleri bölge ofseti kadar saptı, çünkü docProps/core.xml içindeki dcterms:created ve dcterms:modified, Z ile etiketli W3CDTF değerleridir; ECMA-376 Part 2'nin çekirdek özellikler modelinde bu UTC demektir ve HotXLS yerel saati o Z takılıyken damgalıyordu. UTC+8'deki bir makinede 09:30'da oluşturulan bir çalışma kitabı 09:30:00Z taşıyor ve aynı makinedeki Excel onu 17:30'a çeviriyordu. Klasik motorun da FILETIME değerlerinde aynı kusuru vardı ve TXLSXWorkbook.CustomProperties.AddDate (vt:filetime olarak yazılır) üzerinden eklenen özel tarih özellikleri de onu paylaşıyordu. v2.384.48'den beri üç yol da yazmadan önce çevirir ve damga Z taşıdığında okurken geri çevirir; v2.384.59'dan beri okuma tarafı kesirli saniyeleri ve açık +hh:mm / -hh:mm ofsetlerini de işler

Dönüşümün kendisi, naif bir düzeltmenin ters gittiği yerdir. LocalFileTimeToFileTime şu an geçerli olan ofseti uygular; dolayısıyla Temmuz'da çevrilen bir Ocak damgası, yaz saati uygulaması olan her bölgede bir saat sapmış çıkar. HotXLS bunun yerine TzSpecificLocalTimeToSystemTime ve SystemTimeToTzSpecificLocalTime çağırır; ikisi de standart ya da yaz saatini çevrilen tarihe göre seçer ve sıfırdan oluşan atanmamış bir değer dokunulmadan geçer, böylece hiçbir zaman birkaç saat kaymış 1899 tarihine dönüşmez

HotXLS yerelden UTC'ye dönüşüm yolları, Temmuz'da kaydedilen Ocak 17:00 CET damgası için: LocalFileTimeToFileTime bugünün yaz saati ofsetini uygular ve bir saat saparak 15:00Z'ye düşer; TzSpecificLocalTimeToSystemTime ise ofseti damganın kendi tarihinden alır ve doğru 16:00Z'yi yazar
Bölge ofseti makinenin bugünkü kurallarına değil damganın kendi tarihine aittir — bir Windows API yaz saati değişiminin doğru yanını seçer, öbürü ise Temmuz'da çevrilen Ocak damgalarını sessizce bir saat kaydırır
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');
    // Orta Avrupa Saati'ne ayarlı bir makinede core.xml artık şunu taşır
    // <dcterms:created xsi:type="dcterms:W3CDTF">2026-07-01T07:30:00Z</dcterms:created>
    // (Temmuz'da UTC+2); ApprovedOn ise 16:00Z (Ocak'ta UTC+1) olarak yazılır
  finally
    Book.Free;
  end;
end;

HotXLS zaman damgalarını okurken neyi çevirmez?

HotXLS W3CDTF okuyucusu profilin bölge etiketli her biçimini v2.384.59'dan beri çevirir ve hâlâ dokunmadığı tek durum bölgesiz bir saattir. O sürümden önce ayrıştırıcı ilk 19 karakteri alıyor ve 20. karakter Z olduğunda yalnızca UTC'den çeviriyordu; kesirli saniyeli (01:30:00.5Z) ya da açık ofsetli (+08:00) bir damga hiç düzeltme olmaksızın yerel saat olarak okunuyor ve bölge ofseti kadar saptı. HotXLS 2.384.59'dan beri Created, Modified ve tarih değerli özel özellikler her uzunlukta kesirli saniyeyi, Z'yi ve +hh:mm / -hh:mm ofsetlerini ayrıştırır, anı önce UTC'ye sonra yerel saate çevirir ve 2026-07-01 gibi yalnızca tarihli bir damgayı o tarih olarak okur. Saat taşıyıp bölge işareti taşımayan bir damga — W3CDTF profilinin izin vermediği ve ECMA-376 Part 2'nin kural vermediği tür — hâlâ değişmeden yerel saat olarak okunur ve hiç ayrıştırılamayan bir damga sıfır olarak döner. Excel'den geçen çalışma kitapları sağlamdır; bölgeyi atan diğer üreticilerin paketleri nokta denetimi hak eder

Eski HotXLS derlemelerinin yazdığı dosyalar öteki dürüst sınırdır. v2.384.48 öncesi yazılan bir XLSX damgası Z takmış yerel saatti ve dosyada onu doğrusundan ayıran hiçbir şey yoktur; bu yüzden güncel okuyucu onu bölge ofseti kadar kaydırır. O derlemelerin klasik FILETIME damgaları aynı kaymayı alır ve v2.384.17 öncesi yazılan bir oluşturma tarihi ayrıca bir gün geç okunur; çünkü eski sabitin fazla günü de saptanamaz. Yalnızca düzenleme süresi kodlaması ile $0E yerleşiminin tanınabilir bir imzası vardır. Bir de şunu unutmayın: API değeri okumayı yapan makineye göredir; UTC'de çalışan bir servis ile Tokyo'daki bir masaüstü aynı dosya için farklı CreatedDate değerleri bildirir ve ikisi de doğrudur

Belge zaman damgalarını nasıl test etmelisiniz?

Belge zaman damgalarını kendi kodunuzun yazmadığı bir şeye karşı test edin. Bu iki hata da kaydet-sonra-yeniden aç denetimini geçti; çünkü simetrik bir hata, simetrik bir teste görünmez. Excel'in kaydettiği bir çalışma kitabıyla karşılaştırın ya da kaydettikten sonra ham baytları ve XML metnini assert edin ve paketi UTC olmayan bir bölgeye ayarlı makinede, yaz saati değişiminin iki yanından test tarihleriyle çalıştırın. UTC'de koşan bir derleme ajanı eski, bozuk kodu gönül rahatlığıyla geçirecektir

Belge zaman damgaları küçüktür ama kayıt sistemlerinin, arama indekslerinin ve denetim izlerinin sıraladığı şey onlardır; bir gün ya da sekiz saat sapan tarih, eksik bir tarihten kötüdür çünkü kimse onu sorgulamaz. HotXLS Delphi elektronik tablo bileşeni dev aritmetiğini, özellik kimliklerini ve UTC dönüşümünü hem .xls hem .xlsx için üstlenir; kodunuz düz yerel TDateTime değerleri atar ve dosya biçimini kütüphaneye bırakır