menu

Monday, June 9, 2014

Java'da Date ve Time Kullanımında Zone ve DST Offset Değerleri

Java'da Date ve Time Kullanımında Zone ve DST Offset DeğerleriGiriş:     

     Java uygulamalarında Date ve Time kullanımında önerilen yöntem Calendar sınıfının kullanılması ya da Joda kütüphanesinin kullanılmasıdır. Calendar ve özellikle de Joda kullanıldığında Zone ve DST Offset değerleri otomatik dikkate alındığından özellikle 2 Date ya da 2 Time sınıfının farkı alındığı durumlarda hata şansı azalacaktır.Ancak Calendar sınıfının yada Joda kütüphanesinin standart Date sınıfına göre kompleks bir yapıya sahip olması ve  nesne oluşturma maliyeti göz önüne alındığında bazı durumlarda Date ve Time hesapmalarını kendimiz yapmak isteyebiliriz. Bu yazıda bu aşamada dikkat etmemiz gereken bazı noktalara değiniyor olacağız.

1.) Time ve Date farkını alırken Zone farkı:
     Bildiğiniz gibi Türkiye GMT(Greenwich Mean Time) +2 zaman dilimindedir. Direkt Date ya da Time sınıfının getTime() metodu ile "epoch" (01.01.1970) dan şu ana kadar geçen zamanı milisaniye cinsinden almış oluruz. Eğer bu zamanı kullanarak yeni bir Date ya da Time nesnesi oluşturursak bulunduğumuz zaman dilimi +2 olduğundan çıkan sonuca otomatik 2 saat eklenecek ve istediğimiz fark yerine 2 saat fazlasını bulmuş olacağız. Bu durumu aşağıdaki örnekte gözlemleyebiliriz.

SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSS");
SimpleDateFormat sdfTime = new SimpleDateFormat("HH:mm:ss.SSSZ");
Date d = sdf.parse("01.01.2014 23:00:00.000");
Date d1 = sdf.parse("02.01.2014 01:00:00.000");
Date diff = new Date(d1.getTime() - d.getTime());
System.out.println("diff between two dates is: " + sdfTime.format(diff));

Bu programın çıktısına bakarsak beklenen sonuç olan 2 saat yerine 4 saat olduğunu görürüz. Bu sorunu çözmek için ya sdfTime.setTimeZone(TimeZone.getTimeZone("Etc/GMT+0")); kodu ile Formatter sınıfına GMT+0 zaman dilimini vermeli ya da Date nesnesini oluştururken 2 saati çıkararak işlem yapmalıyız.
Date diff = new Date(d1.getTime() - d.getTime() - 2 * 60 * 60 * 1000) . 

Bu durum Calendar sınıfı kullanıldığında da dikkate alınmalı ve Calendar nesnesine zaman dilimi olarak yine GMT+0 verilmelidir. Ayrıca TimeZone JVM seviyesinde de verilebilir.
TimeZone.setDefault(TimeZone.getTimeZone("Etc/GMT+0"));
2.) DST (Daylight Saving Time):
     Bilindiği üzere gün ışığından daha fazla yararlanmak adına her ilkbaharda saatler 1 saat ileri alınmakta ve her sonbaharda da 1 saat geri alınmaktadır.Örneğin 2014 senesinde Türkiye'de 31 Mart 2014 tarihinde saatler 1 saat ileri alındı ve 26 Ekim 2014 Pazar günü de 1 saat geri alınacak. Bu durumda 31 Mart 2014 günü 24 saat değil 23 saat olarak yaşanırken 26 Ekim 2014 günü 25 saat olarak yaşanacak. İşte bu noktada kritik saat hesaplamaları yapan uygulamalarda Calendar yerine direkt Date ya da Time sınıfları kullanılıyor ise DST zamanı dikkate alınmak durumundadır. Örnekle açıklarsak;

SimpleDateFormat sdfAmbigious = new SimpleDateFormat("dd.MM.yyyy");
Date yirmiAltiEkim2014 = sdfAmbigious.parse("26.10.2014");
Date yirmiYediEkim2014 = new Date(yirmiAltiEkim2014.getTime() + (24 * 60 * 60 *1000));
System.out.println("24 saat sonra ertesi güne geçmeliydik : " + sdfAmbigious.format(yirmiYediEkim2014 ));

Bu kodun çıktısına baktığımızda 26.10.2014 tarihine 24 saat eklememize rağmen günün yine 26.10.2014 olduğunu görürüz. Bunun sebebi 26.10.2014 tarihinin 25 saat yaşanacak olmasından dolayı, 24 saat eklendiğinde halen aynı gün içinde kalmasındandır.
Saatlerin ileriye alındığı tarihte ise 23 saat ekleyerek ertesi güne geçilmesi mümkün olacaktır.
Bu durumda kritik gün hesabı yaptığımız ve bir işlemin günde 1 kere yapılmasına izin verdiğimizi düşünelim.Eğer günde 1 kere kontrolünü 24 saat üzerinden yapıyorsak saatlerin 1 saat geriye alınıp  gün 25 saat olduğunda aynı gün 2 işlem yapılmasına izin vermiş olabiliriz(30. dakika da ve 24.üncü saat ve 30.dakika da 2 işlem gelmiş olsun). Aynı şekilde saatlerin 1 saat ileriye alındığı ve günün 23 saat olduğu günde de önceki işlem tarihine bağlı olarak 1 gün önceki işlemi görüp kullanıcıya o gün işlem girilmiş gibi hata dönebiliriz.(Önceki gün 23:59 da işlem girilmiş ise 24 saat sonrasına kadar işlem girişine izin vermediğimizde aradaki 23 saatlik günde hiç işlem girişi yapılamayacaktır.)


Not: Hatırlanacağı gibi yerel seçimler nedeniyle saatler 30.03.2014 tarihinde ileri alınması gerekirken 31.03.2014 tarihinde ileri alınmıştı. JRE TimeZone ayarı normalde 30.03.2014 e ayarlı olduğundan gerekli update yapılmaz ise uygulamalar 30.03.2014 tarihinde saatler ileri alınmış gibi çalışacaktır. (http://www.oracle.com/technetwork/java/javase/tzdata-versions-138805.html)
Update yapılmaz ise uygulama içerisinde 30 Mart için 1 saat ekleme ve 31 Mart için 1 saat çıkarma yapmak gerekecektir.


Not: Zaman hesaplamalarında zamanın başlangıcı  01.01.1970 dir. Yani bir Date nesnesinin getTime() metodu ile dönen long değeri aslında 01.01.1970 den Date nesnenin temsil ettiği zamana kadar olan değeri tutar.01.01.1970 değeri "Epoch" ya da "Unix Time" olarak bilinmektedir ve ilk defa 32 bit Unix makina da kullanılıp standartlaşmıştır.Unix 1969 da geliştirilmiş ve ilk versiyonu 1971 de yayınlanmıştır ve epoch olarak 1970 tarihi seçilmiştir. 32 bit signed integer ile 01.01.1970 den itibaren saydığımızda son gösterilebilecek tarih 2038-01-19 dir. Bu durum 2038 problemi olarak bilinmektedir ve 2038 de halen 32 bit işlemci kullanan makinalarda bu sorun gözlemlenecektir.  

Not: Her durumda web üzerinde çalışan Client-Server uygulamalarında karşılıklı konuşan sistemlerin Zone ve DST ayarlarını kontrol etmek faydalı olacaktır.

Sonuç:
     Diğer tüm programlama dillerinde olduğu gibi java'da da Date ve Time hesaplamalarında Zone, DST farklılıklarını dikkate almak gerekmektedir. Uygulamalarda direkt Date ya da Time sınıflarını kullandığımızda bu farklılıkları kendimiz ele almalıyız. Bu işi Calendar(saat hesaplamalarında time zone verilmelidir.) sınıfı yardımı ile yapabileceğimiz gibi JODA(http://www.joda.org/joda-time/) kütüphanesi kullanarak bu farklılıkların ele alınması işinin çoğunu JODA kütüphanesine de yükleyebiliriz.