Her canlının bir gün ölecek olması kadar kesin olan bir başka şey varsa, o da her yazılım geliştiricinin hayatında en az bir kere tarih ve saatler ile hoş olmayan işlemler yapmak durumunda kalacak olmasıdır.
Bilim insanları, günümüzden yaklaşık 30 sene kadar önce, bilgi teknolojileri alanında tarih saat işlemlerinin sıkıntı oluşturduğunu/oluşturabileceğini düşünerek tarih saat bilgisinin aktarımını uluslararası bir standart olarak belirlemişler.
Uluslararası Standartlar Teşkilatı tarafından 1988 yılında ilan edilen bu standart (ISO 8601), yazılım geliştiriciler olarak farklı gösterimlere sahip yereller veya farklı programlama dilleri arasında oluşabilecek hatalı işlemlerin önüne geçmemizi sağlıyor. Ne yazık ki, son kullanıcıya bu standardı öğretmek ve tarih saat bilgisini yazılıma bu standarda uygun olarak girmesini beklemek gerçekçi bir senaryo değil. Bu durumda kullanıcıdan bu bilgiyi kendi yereline veya öntanımlı bir başka biçime uygun olarak almamız gerekiyor. Geliştirmeleri tamamladıktan sonra kullanıcının girebileceği çeşitli değerler le testlerimizi gerçekleştirelim. Tarih bilgisinin gün/ay/yıl olarak girildiğini varsayalım ve kontrol amaçlı “02/11/2016” ve “02/13/2016” tarihleriyle işlem yapalım.
Neredeyse tüm projelerimizde severek kullandığımız Python programlama dili, ilk tarihi başarılı bir şekilde işleyebiliyor, ikinci tarihte ay olarak 13 geçerli bir değer olmadığı için “değer hatası” (ValueError) veriyor.
In [1]: import datetime
In [2]: datetime.datetime.strptime('02/11/2016', '%d/%m/%Y')
Out[2]: datetime.datetime(2016, 11, 2, 0, 0)
In [3]: datetime.datetime.strptime('02/13/2016', '%d/%m/%Y')
ValueError: time data '02/13/2016' does not match format '%d/%m/%Y'
Yazılım geliştirmede hiç kullanmadığımız ama kullandığımız Redmine, Canvas LMS, GitLab gibi özgür yazılımlara güç veren Ruby programlama dili de Python ile benzer sonuçlar veriyor:
irb(main):001:0> require 'Date'
=> true
irb(main):002:0> DateTime.strptime('02/11/2016', '%d/%m/%Y')
=> #<DateTime: 2016-11-02T00:00:00+00:00 ...>
irb(main):003:0> DateTime.strptime('02/13/2016', '%d/%m/%Y')
ArgumentError: invalid date
Eğer bu istemcinin tarayıcısı üzerinde çalışacak bir kod parçası olsaydı, JavaScript dilinde tarih işlemlerinin vazgeçilmezi Moment.js kütüphanesini kullanırdık. O da geçersiz tarih için “Invalid Date” değerine sahip bir Moment nesnesi oluşturuyor.
> moment('02/11/2016', "DD/MM/YYYY").isValid()
true
> moment('02/13/2016', "DD/MM/YYYY").isValid()
false
PHP’nin belgelendirilmemiş özellikleri
Peki, bu durumda PHP programlama dili ne yapıyor? İlk önce geçerli olan tarihle deneyelim:
php > var_dump(DateTime::createFromFormat("d/m/Y", "02/11/2016"));
object(DateTime)#3 (3) {
["date"]=>
string(26) "2016-11-02 13:15:07.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(15) "Europe/Istanbul"
}
Saat “13:15:07” de nereden çıktı? createFromFormat()
metodunun belgesine bakacak olursanız, biçimde tanımlı olmayan tüm alanların, o anki tarih saat bilgisiyle doldurulduğunu göreceksiniz. Öngörülebilecek hatta çoğu durumda tercih edilecek bir davranış değil belki de ama kabul edilebilir bir mantık içerdiği düşünülebilir. Üstelik yine aynı belgede tarih saat biçimine ünlem işareti “!” karakteri eklendiğinde, tanımlanmayan alanların sıfırlandığı (1970-01-01T00:00:00Z, bkz. Unix zaman) görülebiliyor.
Bakalım, geçersiz bir tarih verdiğimizde ne yapıyor PHP?
php > var_dump(DateTime::createFromFormat("d/m/Y", '02/13/2016'));
object(DateTime)#3 (3) {
["date"]=>
string(26) "2017-01-02 13:15:10.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(15) "Europe/Istanbul"
}
Hata verm… NE?! 2 Ocak 2017 mi? O da nereden çıktı?
Görünüşe göre PHP’nin bu davranışı “bir hata değil, belgelendirilmemiş bir özellik“. DateTime sınıfı, tarih verisini işlerken geçersiz tarihleri geçerli bir tarihe çevirmeye çalışıyor. 12 aydan fazlasını sonraki seneye/senelere veya ayın gün sayısından fazlasını sonraki aylara kaydırıyor. Örneğin, “30/02/2016” tarihi “01/03/2016” olarak algılanırken, artık yıl olmadığı için 2017 yılında aynı tarih “02/03/2017” olarak yorumlanıyor. Girilen tarihin geçersiz olup olmadığını kontrol etmek istiyorsanız getLastErrors()
metodu ile warning_count ve error_count indislerini kontrol ederek işlem yapmanız gerekiyor.
php > $hatalar = DateTime::getLastErrors();
php > var_dump($hatalar);
array(4) {
["warning_count"]=>
int(1)
["warnings"]=>
array(1) {
[11]=>
string(27) "The parsed date was invalid"
}
["error_count"]=>
int(0)
["errors"]=>
array(0) {
}
}
php > var_dump($hatalar['warning_count'] > 0 || $hatalar['error_count'] > 0);
bool(true)
Görseller: alexpokusay / garybaldi / 123RF Stok Fotoğraf
Ali Işıngör
PHP dilinin 30 Şubat gibi geçersiz bir tarih verisini 1 Mart gibi bir tarihe dönüştürmesi ve bunun belgelendirilmemiş bir özellik olması, hakikaten çok acayip!
Kimbilir kaç PHP geliştiricisi kör olmuştur bunu çözmek için…