Bir çizelgede 29 Şubat hücresini göstermek kolay; o hücreye doğru kaydı bağlamak daha önemli. Günün kimliğini “29” metni veya grid'in yirmi dokuzuncu elemanı olarak tutarsam ay ve yıl değiştiğinde model bozulur. Arşivimdeki günlük çizelge ekranlarından hareketle takvim görünümünün arkasına gerçek bir tarih modeli koyuyorum.
Gün ile zaman damgasını ayırmak
“2024 Şubat ayının 29. gününde görev var” bilgisi belirli bir saniyeyi anlatmaz. DateOnly bu tür veriyi saat ve offset eklemeden temsil eder. Bir toplantının başlangıcı ise farklıdır; saat, süre ve gerekiyorsa saat dilimi de ister. Her iki ihtiyacı aynı DateTime alanına doldurup gece yarısını sihirli varsayılan gibi kullanmam.
Aşağıdaki .NET 10 konsol örneği ilk günden başlayarak geçerli ay günlerini oluşturur. Sözlükte anahtar tam DateOnly olduğundan şubatın son günü doğru atamayı bulur. Çıktı önce 29, sonra 2024-02-29:Assigned olur.
var first = new DateOnly(2024, 2, 1);
var records = new Dictionary<DateOnly,string> { [new(2024,2,29)] = "Assigned" };
var days = Enumerable.Range(0, DateTime.DaysInMonth(first.Year, first.Month))
.Select(offset => first.AddDays(offset)).ToArray();
Console.WriteLine(days.Length);
Console.WriteLine($"{days[^1]:yyyy-MM-dd}:{records.GetValueOrDefault(days[^1], "Empty")}");
Hücre dizisini takvim ızgarasına taşımak
days dizisi ayın gerçek günleri. Pazartesi başlayan bir ekran ise ilk haftada önceki aya ait hücreleri de gösterebilir. Görünümün dolgu hücreleri veri modelinin eksik veya geçersiz günleri değildir; onlar da tam tarihle temsil edilir. Örneğin Şubat 2024 perşembe başlar, bu nedenle pazartesi 29 Ocak'tan başlayan bir ızgara kurulabilir.
var leading = ((int)first.DayOfWeek + 6) % 7;
var gridStart = first.AddDays(-leading);
var cells = Enumerable.Range(0, 42)
.Select(offset => gridStart.AddDays(offset))
.Select(date => new
{
Date = date,
InCurrentMonth = date.Month == first.Month && date.Year == first.Year,
Assignment = records.GetValueOrDefault(date)
})
.ToArray();
Burada altı haftalık sabit ızgara bir sunum tercihidir. Aynı model beş haftalık görünümü de destekleyebilir. Kritik nokta, hücreye tıklarken görünen gün numarası yerine Date değerini göndermektir. Ocak 29 ile Şubat 29 aynı “29” etiketine sahip olsa da aynı anahtar değildir.
Tek tarih başına kaç kayıt olabilir?
Sözlük örneği bir güne tek metin atıyor. Aynı gün birden fazla görev, kişi veya vardiya olacaksa DateOnly tek başına yeterli kayıt kimliği olmayabilir. Dictionary<DateOnly, List<Assignment>> gibi gruplanmış bir görünüm kullanılabilir; kalıcı kayıtta ayrıca Id ve gerekiyorsa kaynak kimliği bulunur. Ekrandaki tek hücreyi tek veritabanı satırı sanmamak gerekir.
Veriyi her hücre için ayrı sorgulamak yerine görünür tarih aralığını bir kez okuyup günlere göre gruplarım. Önceki/sonraki aya ait günler de ekranda varsa sorgu sınırını yalnız ayın ilk ve son günüyle sınırlamak bu hücreleri yanlışlıkla boş gösterebilir. Izgaranın gerçek başlangıç ve bitişi veri isteğine yansıtılmalı.
Ay sonunda AddMonths ayrıntısı
Gezinmede seçili ayı ayın ilk günüyle tutmak işleri kolaylaştırır. 31 Ocak'tan AddMonths ile ilerleyip sonraki hareketleri o gün üzerinden sürdürmek ay uzunluklarına bağlı gün kaymaları yaratabilir. Ay görünümünün durumu “ayın 1'i”, seçilen hücrenin durumu ise ayrı bir tarih olursa iki niyet birbirine karışmaz.
Veri kaynağı zaman damgası taşıyorsa onu hangi iş saat dilimine göre güne çevirdiğimi belirtirim. UTC gece yarısına yakın bir olay başka bir bölgede önceki gün olabilir. Salt gün olarak saklanan bir kaydı ise tekrar UTC'ye çevirerek gününü kaydırmak istemem.
Takvimi küçük ama zor tarihlerle sınamak
Şubat 2023 için 28, Şubat 2024 için 29 gün beklerim. Pazartesi başlayan ayda sıfır dolgu günü, yıl geçişinde doğru önceki/sonraki yıl tarihleri gelmeli. Kaydı olmayan gün Empty görünmeli; başka bir günün ataması kaymamalı. Geçersiz 31 Şubat girdisi takvim hesabına teslim edilmeden reddedilmeli. Bunlar görsel ekran görüntüsünden önce model düzeyinde sınanabilir.