Tag Archives: DateTime

DateTime.Parse(string) kullanmayı bırakamaz mıyız?

gandalf-parse

Scott Hanselman’ın What Great .NET Developers Ought To Know (More .NET Interview Questions) isimli yazısında da bahsedilği gibi, başta Stack Overflow olmak üzere birçok online sitede aşağıdaki şekilde kod örneklerine rastlıyorum:

DateTime dt = DateTime.Parse(myString);

Buna bir son verilmesi lazım. Bu kullanımda bir problem var. DateTime.Parse(string) metodu default olarak CurrentCulture ayalarını kullanmaktadır. Ve bu ayarlara istinaden direkt olarak bir string’i göndermek belirsiz durumlar oluşturmaya zemin hazırlar. Bu şu demek gibi;

Sana bir string gönderiyorum. CurrentCulture ayarlarına istinaden geçerli formatları sırasıyla dene, eğer biri uyuyorsa bu şekilde parse et. Eğer bu string için bu culture ayarlarında uygun bir format yoksa, bir FormatException fırlat.

Örnek olarak “01/02/2015” string’ini örnek alalım. Bu string hangi formatta? 1 Şubat 2015 mi? 2 Ocak 2015 mi? Cevap vermek imkansız zira bu sorunun cevabı dünyanın neresinde yaşadığınıza ve/veya hangi culture ayarlarını kullandığınıza göre değişir. Şehir/bölge olarak eşleştirilen Türkiye (“tr-TR”) ve Hindistan (“hi-IN”) için kullanılan culture ayarlarına ve bölgesel algıya göre bu string “1 Şubat 2015” olarak parse edilir fakat Amerika Birleşik Devletleri (“en-US”) için kullanılan culture ayarına göre “2 Ocak 2015” şeklinde parse edilir.

Bu DateTime.Parse(string) metodunun .NET Framework 4 versiyonu MSDN sayfasında (gördüğüm kadarıyla bu açıklama .NET Framework 4.5 ve 4.6 versiyonunda mevcut değil) da ifade edilir:

Because the Parse(String) method tries to parse the string representation of a date and time using the formatting rules of the current culture, trying to parse a particular string across different cultures can either fail or return different results. If a specific date and time format will be parsed across different locales, use the “DateTime.Parse(String, IFormatProvider)” method or one of the overloads of the ParseExact method and provide a format specifier.

Burada gördüğünüz gibi, spesifik bir format ile DateTime.ParseExact metodunu kullanmak çok daha kontrollü ve kesin bir sonuç döndürür. Tabi bu durumu gelen verinin formatını kesin olarak doğru bildiğimiz zamanlar kullanmalıyız. Eğer güvenmediğimiz veya bilmediğimiz bir yerden bu veriyi alıyorsak DateTime.TryParse veya DateTime.TryParseExact metodları daha uygun olur.

Açıklamada farklı sonuçlar oluşabileceği veya “fail” yani exception alabileceğimiz söyleniyor. Tabi ki bu çok normal. Zira “01/02:2015A22-22” gibi standart olmayan bir string gönderirsek, hangi culture ayarlarını kullanırsak kullanalım bir exception alırız ve bu exception’ı kod tarafıdan handle etmek zorunda kalırız. Standart olmayan veri dışında kullandığımız culture’ın DateTimeFormatInfo.Calendar özelliğinde var olan takvimin desteklediği sınırları aştığı durumlarda veya bu takvim üzerinde var olmayan bir tarihi parse etmeye çalıştığımızda da bu exception’ı alırız.

.NET Framework 4.5 versiyonunda çalıştırdığım şu kodu örnek alalım:

foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
    DateTime dt;
    if (!DateTime.TryParse("1500/12/29", culture, DateTimeStyles.None, out dt))
    {
        Console.WriteLine(culture.Name);
    }
}

Bu kod parçası normalde konsola harhangi bir şey yazdırmaz (eğer bu formatı desteklemeyen custom bir culture oluşturmadıysanız). Bu demek oluyor ki bu string formatı bir şekilde lokal makinanızdaki tüm culture ayarları tarafında parse edilebilir. Şimdi de string değerini “1500/12/30” olarak değiştirelim.

foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
    DateTime dt;
    if (!DateTime.TryParse("1500/12/30", culture, DateTimeStyles.None, out dt))
    {
        Console.WriteLine(culture.Name);
    }
}

Bu kod 6 adet sonuç üretiyor. TwoLetterISOLanguageName bakımından benzer olanları saymazsak bunlar “prs-AF” ve “yo-NG” değerleri. Peki bunların ortak özellikleri ne? Bu iki CultureInfo değeri DateTimeFormatInfo.Calendar olarak HijriCalendar kullanmakta. Ve bu takvimde 12. ay olan Zulhijjah (diğer adıyla Dhu al-Hijjah) sadece artık yıllarda 30 gün vardır. Diğer yıllarda 29 gün sürer bu ay. Bu arada HijriCalendar içerisinde Miladi takvime göre farklı bir artık yıl hesaplama kuralı vardır. Bu nedenle aslında bu takvimde var olmayan bir tarihi parse etmeye çalıştığımızdan exception almış oluruz. Son olarak string değerini “1500/12/31” olarak değiştirelim.

foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
    DateTime dt;
    if (!DateTime.TryParse("1500/12/31", culture, DateTimeStyles.None, out dt))
    {
        Console.WriteLine(culture.Name);
    }
}

Üstteki kod parçasında kullandığımız ”30” değeri için aldığımız sonuçlara ilave olarak “ar-SA” culture’ını da burada alırız. Neden? Çünkü bu culture değeri DateTimeFormatInfo.Calendar olarak UmAlQuraCalendar kullanmakta ve bu takvimin desteklediği maksimum tarih değeri “1500/12/30”. Bunu .NET Framework kodunu browser üzerinden gözlemleyebileceğiniz reference source içerisinde görebilirsiniz. Bu nedenle bu takvimin desteklediği tarih aralığı dışında bir tarih parse etmeye çalıştığımızdan exception almış oluruz. Aslında yukarıdakine benzer bir durum fakat yine de bahsetmek istedim.

    ////////////////////////////////////////////////////////////////////////////
    //
    //  Notes about UmAlQuraCalendar
    //
    ////////////////////////////////////////////////////////////////////////////
     /*
     **  Calendar support range:
     **      Calendar    Minimum     Maximum
     **      ==========  ==========  ==========
     **      Gregorian   1900/04/30   2077/11/17
     **      UmAlQura    1318/01/01   1500/12/30
     */

Bunların dışında DateTime.Parse(string) kullanımında, CurrentCulture konusunda kesin bir bilgi sahibi olunmasına kesin gözüyle bakamadığımızdan, “AM/PM” belirteçleri, tarih ayıracı, zaman ayıracı, gün isimleri, ay isimleri, kısaltılmış gün isimleri, kısaltılmış ay isimleri vb. özellikler culture bazlı farklılıklar gösterir. Örneğin; “Pazartesi” içeren bir string’i nasıl “en-US” culture’ı ile parse edemiyorsak, “Monday” içeren bir string’i de “tr-TR” culture’ı ile parse edemeyiz.

CurrentCulture ayarı konusunda kesin bilgimiz olsaydı bu herşeyi çözer miydi? Pek değil aslında. .NET Framework üzerindeki major ve minor değişiklikler veya işletim sistemi versiyonu değişikliklerinde bile CultureInfo özellikleri değişebiliyor. Bu konuda en önemli değişikliklerden biri it-IT culture’ı için .NET 3.5 versiyonundan 4.0 versiyonuna geçişte yaşandı. .NET 3.5 ve önceki versiyonlarda bu culture TimeSeparator olarak “.” kullanmasına rağmen .NET 4.0 versiyonu ile birlikte “:” kullanımına geçti. Date and time notation in Italy sayfasına göre de doğru olan “:” gibi görünüyor. Benzer durumlar Windows 10’a ilk geçiş sürecinde de yaşandı. Reddit’te benzer bir thread dahi mevcut. Ek olarak Culture data shouldn’t be considered stable (except for Invariant) yazısını da okuyabilirsiniz. Bu durum özet olarak CultureInfo sayfasında “Dynamic culture data” kısmında şu şekilde açıklanmış:

Except for the invariant culture, culture data is dynamic. This is true even for the predefined cultures. For example, countries or regions adopt new currencies, change their spellings of words, or change their preferred calendar, and culture definitions change to track this. Custom cultures are subject to change without notice, and any specific culture might be overridden by a custom replacement culture. Also, as discussed below, an individual user can override cultural preferences. Applications should always obtain culture data at run time.

When saving data, your application should use the invariant culture, a binary format, or a specific culture-independent format. Data saved according to the current values associated with a particular culture, other than the invariant culture, might become unreadable or might change in meaning if that culture changes.

Metinsel (string) bir veriyi Ticks bazlı bir veri yapısına (DateTime) parse etmenin karmaşıklığı yanı sıra, biz programcılar, kullandığımız culture ayarlarının bu gibi belirsiz değişimlere maruz kalabileceğini (InvariantCulture için bile olsa) unutmamalıyız. Bu nedenle nacizane fikrimce en iyi yaklaşım kendi static formatımızı oluşturmak veya değişimi düşük bir ihtimal olsa da InvariantCulture kullanmaktır. Yazdığımız bütün programların kendilerine özgü gereksinimleri olacaktır, bu yüzden bunları seçerken dikkkatli olmalıyız.

Ne ki Tayland’ın derdi?

Johs Susser gerçekten çok geyik bir programcı. Aynı zamanda iyi de bir girişimcidir. Geçenlerde gönderdiği tweet ile uzun zamandır üzerinde düşündüğüm bir konuya değindi kendisi.

Eğer sizde şimdiye kadar en az bir defa değişik zaman dilimlerinde çalışan bir yazılım projesinde görev aldıysanız, muhtemelen siz de Josh’un bu fikrine katılıyorsunuzdur. Projenizdeki dosyaların server’lara yüklenme zamanlarını ve bunları farklı zaman diliminde yaşayan insanlara göstermekte zorluklar yaşayabilirsiniz. Özellikle .NET programcıları için DateTime sınıfının bazı sorunları olduğundan bu durum daha karmaşık hale gelebilir. Şöyle belirginleştirelim bu durumu. Aşağıdaki kod parçasını ele alalım;

 Console.WriteLine(DateTime.Now == DateTime.UtcNow); 

Aslına bakarsanız başlarda bu kodun ne yapması gerektiğini bir türlü kavrayamamıştım. Fakat biraz daha üzerinde kafa yormaya başlayınca önümde 3 seçenek belirmeye başladı;

  • Her zaman True yazdırır, ikisi de aynı zamanı gösterir, sadece biri local zamanı diğeri de evrensel zamanı.
  • Her zaman False yazdırır, bu iki değer farklı çeşit veriyi ifade eder, bu yüzden bunlar otomatik olarak eşit olamazlar.
  • Eğer yerel zamanınız UTC ile senkronize ise, zaman dilimine uyulmaksızın True döndürür ki bu iki değer eşittir.

Bu kod benim derlediğim 4 farklı yerdeki derleyicide de False sonucunu döndürdü. Kendi bilgisayarımdaki derleyicide False sonucunu döndürmesi çok doğal tabi ki, zira İstanbul UTC+02.00 zaman diliminde olduğundan bu iki değer hiçbir zaman aynı zamanı göstermezler. Herneyse, DateTime hakkında bu kadar dedikodu yeter. Şimdi asıl konumuza dönelim.

Tayland..

Bildiğiniz gibi dünya üzerinde kullanılan birden fazla takvim var. Bu takvimlerden birçoğu etkili bir insanın doğum günü ile başlar. Standart uluslararası takvim Hz. İsa’nın doğum yılını 1 olarak kabul eder, ondan önceki her yıl milattan önce kabul edilir. Budist takvimi Buddha’nın ölümü ile başlar.

Fakat Tayland takviminde göre Buddha’nın 2556 yıl önce vefat ettiğini, yani Hz. İsa’dan 543 sene önce vefat ettiğini belirtir. Bu yüzden eğer Miladi takvim ile Tayland’ta hangi yıl olduğuna bakmak isterseniz miladı yıla 543 yıl eklemeniz gerekir.

 2013 + 543 = 2556 

Peki kod ile bu yıla ulaşmaya çalışsak? Çok basit. Yeni bir ThaiBuddhistCalendar() nesnesi oluşturup bunun GetYear() methodunu şu anki local zaman parametresi ile çalıştırabilirz ;


Console.WriteLine(new ThaiBuddhistCalendar().GetYear(DateTime.Now)); // 2556

Peki bunu bir de DateTime nesnesi oluşturup ulaşmaya çalışsak?


DateTime d = new DateTime(2013, 1, 1, new ThaiBuddhistCalendar());
Console.WriteLine(d.Year); // 1470

Görüldüğü üzere, burada Budist takvimine göre bir DateTime nesnesini oluşturmamıza rağmen, Year özelliği 1470 değerini gösterir. Çünkü DateTime nesnesi, hangi takvimi kullanacağını yorumlayamaz. Varsayılan olarak her zaman Miladi Takvimi kullanır. Bu yüzden Budist takvimine göre 2013 yılı için oluşturduğumuz DateTime nesnesinin Year özelliği Budist takviminde 2013 yılının Miladi takvimde 1470 yılını gösterdiği için bu değeri alır.

Eğer günlük zaman kaydetme işlemlerinde ya da zaman dilimleri hakkında en iyi çözümleri öğrenmek isterseniz aşağıdaki konuya bir göz atmanızı tavsiye ederim;

Peki neden Tayland?

Aslında ThaiBuddhistCalendar()’ı kullanan en temel ülke olmasının yanı sıra asıl neden başka..

The Hangover Part II

Peki ya başlık?

Başlığın ilham kaynağı da Türk sinema tarihinin gülen adamı Kemal Sunal’dan gelsin:

feyzo : bismillahirahmanirahim. hoca efendi anam sana gelecek.
haco efendi : ne ki ananın derdi ?
feyzo : der bende deva sende. bismillahhhh.. ” taaaak ! biloya tokat. ”
bilo : ne itip duriysen ula ?
feyzo : sus günah abdest alıyosun eşşolu eşşek. neuzübillah.. beni okutacak sana, kara sevda olmuşam çünkim.
haco : heç öyle bi hal görmiyem sende.
feyzo : bi yüzlük var sana helalinden. çaremde şu güloyla evlenmek. anamı razı et.
haco : çok az. iki yüzlük bide horoz, hemide iri.
feyzo : nerden bulam horozu ?! 150 gayme verem sulf olak.
haco : kurtarmaz. bide tavuk.
feyzo : anlaştık.