Bir elektronik tabloyu açın, 2026-06-19 gösteren bir hücreye tıklayın; formül çubuğu hala bir tarih okur. Aynı hücreyi Delphi'den okuduğunuzda 46192 sayısını alırsınız. Her iki görünüm de doğrudur, çünkü Excel o hücrede asla bir tarih saklamadı. Bir seri numarası, yani bir gün sayısı sakladı ve ekrana bu sayıyı bir takvim tarihi olarak işlemesini söyleyen bir sayı biçimi bağladı. Hücre değerinde bir tarih türü yoktur. Bir sayı and bir görüntüleme kuralı vardır; görüntüleme kuralı, bir tarihi düz bir miktardan ayıran tek şeydir
Bu ayrım, bir elektronik tablo kitaplığının kaçınması gereken her tarih hatasının köküdür. Seri numarası tek başına hangi gün olduğunu söylemez, çünkü sıfırıncı günün hangi gün olduğunu söylemez. Aynı sayı, tek bir çalışma kitabı bayrağına bağlı olarak dört yıl arayla iki tarih anlamına gelir. Ve bir tarih olarak geri okunması gereken bir sayı, birisi biçimini inceleyip bir tarih deseni tanımadığı sürece çıplak bir miktar olarak geri okunacaktır. HotXLS'teki tarih modeli bu şekilde oluşturulmuştur ve bu yüzden böyle olması gerekir
Bir tarih hücresi bir sayı artı bir biçimdir
Excel, bir tarihi bir epoğun başlangıcından bu yana geçen gün sayısı olarak saklar ve günün saatini de kesirli kısımda tutar. Seri numarası üzerindeki gün ortası .5 taşır. Tamsayı kısmı gün sayısıdır. Saklanan değerde onu zamansal olarak işaretleyen hiçbir şey yoktur. Onu işaretleyen şey hücrenin sayı biçimidir: ECMA-376 buna numFmt der ve biçim kodu bir tarih veya saat deseni açıklayan bir hücre tarih olarak gösterilir. Biçimi kaldırdığınızda aynı hücre bir sayı gösterir; altta yatan değer asla değişmemiştir
Bir hücre değerini okumanın size bir varDate veya düz bir Double olabilen bir Variant vermesinin ve aynı hücredeki sayı biçiminin bir üçüncü tarafın hangisini kastettiğine karar veren sinyal olmasının nedeni budur. HotXLS bir XLSX dosyasını açtığında, bir hücre hem Value hem de NumberFormatIndex özelliklerini TXLSXCell içine taşır ve biçim dizini, sayının bir tarih olup olmadığını öğrenmek için başvurduğunuz şeydir
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]; // row 1, col 1 (1-based)
// Value may arrive as varDate or as a plain numeric serial;
// the format index is the signal that tells them apart.
Writeln('raw value : ', VarToStr(Cell.Value));
Writeln('numFmt idx: ', Cell.NumberFormatIndex);
Writeln('format : ', Cell.NumberFormat);
finally
Book.Free;
end;
end;
Aralarında 1462 gün olan iki epok
Her Windows çalışma kitabının kullandığı varsayılan tarih sistemi, 1899'un en sonundan itibaren sayar, böylece 1 seri numarası 1900'ün ilk gününe düşer. Diğer sistem ise eski Macintosh'a dayanır ve 1904'ün başlangıcından itibaren sayar, bu nedenle 1 seri numarası dört yıl ve bir gün sonrasıdır. Bir çalışma kitabı hangi sistemi kullandığını tek bir bayrakta kaydeder. Bir OOXML paketinde bu bayrak, çalışma kitabı parçası üzerindeki date1904'tür; HotXLS bunu çalışma kitabının Date1904 özelliği olarak yüzeye çıkarır
İki epok arasındaki fark tam olarak 1462 gündür. Bu; üçü 365 günlük, biri 366 günlük olmak üzere toplam 1461 gün eden dört takvim yılı artı iki gün sıfır kuralı arasındaki bir gün ve birazlık kayma için bir gün daha demektir. Sayı sabittir ve bunu aklınızda taşıyabilirsiniz. Önemi sıfır olmamasıdır. 1904 çalışma kitabından kopyalanan ve 1900 kurallarına göre yorumlanan bir seri numarası (veya tam tersi) her tarihi 1462 gün kaydırır; bu da dört yıldan biraz fazla hatalı olan ve bozuk veriyle karıştırılması kolay tarihler olarak kendini gösterir
Delphi'deki TDateTime 1900 kuralına bağlı olduğundan, Excel serilerini TDateTime'a eşleyen bir kütüphane, çalışma kitabı 1904 olarak bayraklandığında her iki yönde de 1462 kadar kaydırmak zorundadır. Bir 1904 serisini okurken, onu bir TDateTime olarak ele almadan önce 1462 çıkarın; bir 1904 çalışma kitabına TDateTime yazarken, Excel'in kastettiğiniz günü işlemesi için seriden 1462 çıkarın. HotXLS, Date1904 özelliği ayarlanmış bir çalışma kitabı için tarih değerlerini serileştirdiğinde bu kaymayı dahili olarak uygular, böylece TDateTime olarak atadığınız değer ekranda aynı takvim gününe geri döner
Kasıtlı 1900 artık yıl tuhaflığı
1900 sisteminde ünlü bir pürüz vardır. Excel 1900'ü artık yıl olarak ele alır ve 29 Şubat 1900'ü gerçek bir tarih, seri numarası 60 olarak kabul eder. 1900 yılı artık yıl değildi, çünkü yüzyıl yılları yalnızca 400'e bölünebildiğinde artık yıldır ve 1900 bölünemez. Hayali gün, hatayla birlikte gelen erken bir elektronik tablodan miras kalan, seri aritmetiğinin onlarca yıllık dosyalar arasında aynı kalması için o zamandan beri sürdürülen kasıtlı bir uyumluluk davranışıdır
Pratik sonuç küçük ama gerçektir: 1 Mart 1900 veya sonrasındaki herhangi bir tarih için seri numarası, kesinlikle doğru bir gün sayısının vereceğinden bir fazladır, çünkü var olmayan 29 Şubat bir sayı tüketmiştir. Bir elektronik tablo kitaplığı bu tuhaflığı düzeltmek yerine yeniden üretir, çünkü Excel'in aritmetiğiyle tam olarak eşleşmek işin tamamıdır. Bunu düzeltmek, modern her tarihi Excel'in gösterdiğinden bir gün uzağa yerleştirir; bu da ticari kullanımda hiçbir gerçek tarihin dokunmadığı kırk bin günlük bir günlük sapmayı taşımaktan daha kötü bir sonuçtur. 1904 sisteminde eşdeğer bir hayali gün yoktur, bu da bazı işletmelerin tarihsel olarak onu tercih etmesinin bir nedenidir
numFmt'ten tarih algılama
Bir sayı başkasının yazdığı bir dosyadan geldiğinde, onun bir tarih olduğunun tek kanıtı biçimidir. ECMA-376, anlamı spesifikasyon tarafından sabitlenmiş bir yerleşik biçim kimlikleri (IDs) bloğu atar ve tarih ile saat biçimleri bilinen aralıkları işgal eder. 14 ila 22 arasındaki kimlikler genel yerel tarih ve saat biçimleridir (tanıdık m/d/yyyy, h:mm ve benzerleri). 45 ila 47 arasındaki kimlikler geçen süre (elapsed-time) biçimleridir. Diğer iki grup olan 27 ila 36 ve 50 ila 58, ECMA-376 18.8.30'da tanımlanan CJK takvimleri için kullanılan yerel tarih ve saat biçimleridir. Sayı biçimi kimliği bu aralıklardan herhangi birine düşen bir hücre, tarih veya saat hücresidir
Yerleşik kimlikler yaygın durumları kapsar ancak özel olanları kapsamaz. Bir çalışma kitabı kendi biçim kodunu tanımladığında (örneğin standart dışı bir sıralama veya yerelleştirilmiş bir ay adı), kimlik yerleşik aralığın üzerindedir ve çalışma kitabının sayı biçimi tablosunu işaret eder. Bunlar için bir tarihi tanımak, biçim kodu dizesini okumak ve tarih belirteçlerini aramak anlamına gelir. HotXLS her iki kontrolü de tek bir dahili yüklemde (XlsxNumFmtIsDate) birleştirir; bu, yerleşik tarih aralıkları için hemen true döndürür ve aksi takdirde özel biçim kodunu XlsxFormatCodeIsDate aracılığıyla ayrıştırır. Bunun genel tarafı, hücrenin size hem çözümlenmiş biçim kodunu hem de test edilecek kimliği veren NumberFormat dizesi ve NumberFormatIndex değeridir
Biçim ayrıştırıcısı neden yalnızca d ve m'yi tarayamaz
Bir biçim kodunu tarih belirteçleri için ayrıştırmak, bir sayı biçiminde başka nelerin yaşadığını hatırlayana kadar önemsiz görünür. Tarihleri heceleyen harfler için naif bir arama (gün, ay, yıl, saat ve saniye için d, m, y, h ve s), hiç de tarih belirteci olmayan iki yapıda yanlış tetiklenecektir
Birincisi tırnak içindeki dize sabitidir (literal). Bir sayı biçimi, çift tırnak içine değişmez metin gömebilir, bu nedenle #,##0 "MM" gibi bir finansal biçim, hiçbir zamansal anlamı olmaksızın bir sayıya M ve M karakterlerini ekler. Tırnak içindeki harfleri ay belirteçleri olarak sayan bir tarayıcı, bu para birimi biçimini yanlışlıkla bir tarih olarak işaretleyecektir. İkincisi parantez bölümüdür. Sayı biçimleri köşeli parantez içinde yönergeler taşır ([Red] gibi renk adları, [>1000] gibi karşılaştırma koşulları, yerel etiketler ve geçen süre işaretçileri [h] ve [mm]). Bazı parantez içerikleri tarih harflerini tutarken bazıları tutmaz ve parantez içindeki metni biçimin gövdesiyle aynı şekilde ele almak hem yanlış pozitiflere hem de kaçırılan durumlara yol açar
Doğru ayrıştırıcı, biçim kodunu karakter karakter dolaşır, tırnak içindeki bir değişmezin içinde olup olmadığını ve parantez yuvalamasının ne kadar derine gittiğini izler; ayrıca takip eden tek bir karakteri tırnak içine alan ters eğik çizgi kaçışını (backslash escape) onurlandırır. Yalnızca herhangi bir dize değişmezinin dışında ve herhangi bir parantez bölümünün dışında bulunan kaçışsız bir tarih harfi gerçek bir tarih belirteci olarak sayılır. XlsxFormatCodeIsDate tam olarak bu şekilde tarar: bir tırnak işareti, kapanış tırnağına kadar belirteç algılamayı baskılayan bir değişmez-içi durumunu değiştirir, ters eğik çizgi bir sonraki karakteri atlar ve bir parantez derinliği sayacı [...] çalışmalarının içindeki algılamayı baskılar. Kazanç, #,##0 "MM" ifadesinin doğru bir şekilde sayı biçimi olarak okunması, tırnak işaretlerinin dışında tek bir m veya d'den başka bir şey içermeyen kısa bir özel kodun ise hala doğru bir şekilde tarih olarak tanınmasıdır
Üçüncü taraf dosyalardan tarihleri okuma
Yukarıdaki her şey tek bir iş akışında birleşir: başka bir uygulamanın yazdığı bir sayıyı güvenebileceğiniz bir tarihe dönüştürmek. Seri numarası size gün sayısını verir, çalışma kitabının Date1904 bayrağı sayımın hangi epoğa göre ölçüldüğünü söyler ve hücrenin sayı biçimi kimliği veya özel kodu, sayının ilk etapta bir tarih olarak kastedildiğine dair tek kanıttır. Bu üçünden herhangi birini bırakırsanız, görünür bir hata yerine makul bir yanlış cevap alırsınız
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;
Eski BIFF tarafında adlandırılmaya değer bir ekstra tuzak daha vardır. Daha eski bir .xls akışında, bitişik sayısal hücrelerin bir dizisi, tek bir yapıda biçim referanslarıyla birlikte birkaç değeri saklayan tek bir çoklu hücre kaydı olan MULRK içinde paketlenebilir. Bu şekilde saklanan tarih hücreleri, paketlendikleri için tarih olmaktan çıkmazlar, bu nedenle aynı biçim kimliği testi çoklu hücre kaydının içine ulaşmalı ve hücre başına uygulanmalıdır; 1904 kayması hala ürettiği her seri numarasını yönetir. Yalnızca bağımsız sayı kayıtlarını inceleyen ve paketlenmiş olanları atlayan bir okuyucu, bir tarih sütununu sessizce bir tamsayı sütununa dönüştürecektir
Pratikte seri numaralarını TDateTime'a eşleme
Biçim kontrolü bir tarihi onayladıktan ve Date1904 bayrağı bilindikten sonra dönüşüm mekaniktir. HotXLS'in zaten bir varDate olarak geri verdiği değer, doğrudan kullanabileceğiniz bir TDateTime'dır. Kaynak, tanınan bir tarih biçimi olmadan bir seri numarası yazdığında gerçekleşen düz bir Double olarak gelen bir değer, 1900 ekseninde bir gün sayısı olarak okunarak ve 1904 çalışma kitabı için önce 1462 günlük kaymanın çıkarılmasıyla (böylece epoklar hizalanır) dönüştürülür. Diğer yöne giderken, bir hücreye TDateTime atamak 1900 tabanlı seri numarasını saklar ve çalışma kitabı 1904 olarak bayraklandığında HotXLS kaydetme sırasında aynı 1462 günlük kaymayı uygular; böylece kaydedilen dosya dört yıl sapmış bir tarih yerine kastettiğiniz tarihi gösterir
Bir çalışma kitabı oluştururken bayrağı kasıtlı olarak ayarlayın. Varsayılan değer Date1904'ü false bırakır; bu, Windows için Excel ile eşleşir ve neredeyse her zaman istediğiniz şeydir. Bunu yalnızca Mac kaynaklı bir çalışma kitabını yeniden üretirken veya akış yönündeki bir sistem özellikle 1904 eksenini beklediğinde true olarak ayarlayın. Dört yıllık hataların tüm sınıfını önleyen tek kural tutarlılıktır: çalışma kitabı başına epoğu bir kez seçin, her tarihi onun altında yazın ve her seri numarasını dosyanın gerçekte taşıdığı bayrak altında geri okuyun
Tarihler, bir hücrenin gerçekte ne tuttuğuna dair daha geniş bir hikayedeki bir sütundur. Izgaranın yanında yer alan komşu meta veri katmanı (başlık, yazar ve zaman damgaları), çalışma kitabı meta verileri ve belge özellikleri hakkındaki makalemizde ele alınmıştır; burada aynı Created and Modified değerleri, aynı ayarlanmamışsa-sıfıra-eşittir kuralıyla TDateTime olarak saklanır. Bir tarih saklanan bir değerden ziyade bir hesaplamanın sonucu olduğunda, formül motoru ve özel işlevler hakkındaki makalemizdeki değerlendirme kuralları, biçimin daha sonra işlediği seri numarasını belirler. Her ikisi de, Excel otomasyonu olmadan XLS ve XLSX tarihlerini okuyan ve yazan Delphi ve C++Builder için HotXLS Bileşeni'nde sunulan aynı tarih modeli üzerinde çalışır