Saat Dilimi Hatalarından Kurtulmak: UTC Disiplini

Saat Dilimi Hatalarından Kurtulmak: UTC Disiplini

Rapor bir gün kayıyor, gece yarısı çalışan iş iki kez çalışıyor, bildirim yanlış saatte gidiyor. Üçünün de kaynağı aynı ve çözümü tek bir kural.

Saat dilimi hataları, hata ayıklaması en sinir bozucu sorun sınıfı; çünkü hiçbir istisna fırlatmıyorlar. Kod çalışır, sayfa açılır, rapor gelir — sadece rakamlar bir gün kaymıştır. Aylık ciro raporu ayın birinci gününü eksik, otuz birinci gününü fazla sayar ve bunu kimse fark etmez.

Saat dilimi yönetiminde temel kurallar
Kural sayısı üç; ihlal edildiğinde ortaya çıkan hata sayısı çok daha fazla.

Tek kural: UTC sakla, yerelde göster

Bütün disiplin bu cümlede. Veritabanına yazılan her an UTC'dir; kullanıcıya gösterilen her an, o kullanıcının kendi dilimine çevrilmiştir. Ara bir yerde dönüşüm yapılmaz.

Kural basit ama uygulaması üç noktada birden tutarlılık ister: uygulama, veritabanı bağlantısı ve işletim sistemi. Üçünden biri farklı ayarlıysa veriler sessizce kayar.

PHP
// app/Config/App.php — uygulamanın tek doğru saati
public string $appTimezone = 'UTC';

// Veritabanı bağlantısında da aynı diliml
// (MySQL oturumu sunucunun yerel saatini varsayarsa TIMESTAMP alanlar kayar)
'DBDriver' => 'MySQLi',
'charset'  => 'utf8mb4',
// bağlantı sonrası: SET time_zone = '+00:00'

Gösterim katmanı

Çevrim yalnızca kullanıcıya gösterirken yapılır. Kullanıcının dilimini nereden alacağınız ürün kararıdır: profil ayarından, tarayıcıdan otomatik ya da tek dilimli bir ürünse sabit değerden.

PHP
function yerelGoster(string $utcTarih, string $dilim = 'Europe/Istanbul'): string
{
    $d = new DateTimeImmutable($utcTarih, new DateTimeZone('UTC'));

    return $d->setTimezone(new DateTimeZone($dilim))->format('d.m.Y H:i');
}

Şablonlarda ham tarih basmayın; her yerde bu tek fonksiyondan geçirin. Aksi hâlde uygulamanın bir köşesinde çevrilmemiş bir tarih kalıyor ve bulunması saatler alıyor.

Asıl tuzak: gün sınırları

Raporlarda hatanın kaynağı neredeyse her zaman burası. "Bugünkü siparişler" sorgusu şöyle yazıldığında yanlıştır:

SQL
-- Yanlış: UTC gününe göre filtreliyor
SELECT * FROM siparis WHERE DATE(olusturma_at) = CURDATE();

Türkiye UTC+3 olduğu için, yerel saatle 00:00-03:00 arasında verilen siparişler UTC'de bir önceki güne düşer. Yani "bugünkü" rapor, sabahın ilk üç saatini bir önceki güne yazar.

Doğrusu, gün sınırlarını kullanıcının diliminde hesaplayıp UTC'ye çevirerek sorgulamaktır:

PHP
$dilim = new DateTimeZone('Europe/Istanbul');
$bas   = (new DateTimeImmutable('today', $dilim))->setTimezone(new DateTimeZone('UTC'));
$bit   = $bas->modify('+1 day');

$builder->where('olusturma_at >=', $bas->format('Y-m-d H:i:s'))
        ->where('olusturma_at <',  $bit->format('Y-m-d H:i:s'));   // < kullanın, BETWEEN değil

Son satırdaki ayrıntı önemli: BETWEEN kullanıp bitişi 23:59:59 yazmak, o saniyeden sonraki ve gün dönmeden önceki kayıtları atlar. Yarı açık aralık (>= ve <) bu sınıftaki hataların tamamını kapatıyor.

Zamanlanmış işler ve yaz saati

Yerel saate göre çalışan cron işlerinde yılda iki kez sorun çıkar. Saatlerin ileri alındığı gece, 03:00'e ayarlı iş hiç çalışmaz — o saat o gün yoktur. Geri alındığı gece ise 03:00 iki kez yaşanır ve iş iki kez çalışır.

Bunun somut sonucu, iki kez gönderilen fatura maili veya iki kez işlenen ödeme oluyor. İki koruma birlikte kullanılmalı: zamanlayıcıyı UTC'ye göre kurun ve işin kendisini aynı gün için iki kez çalıştırıldığında zarar vermeyecek şekilde yazın. Zamanlayıcı tasarımı hakkında otomatik dağıtım kurgusu yazısındaki idempotency mantığı burada da geçerli.

Geleceğe randevu: istisna

"UTC sakla" kuralının tek istisnası ileri tarihli randevular. Bir ülkenin saat dilimi kuralı değişebiliyor; bugünkü kurala göre UTC'ye çevirdiğiniz "15 Mart 14:00" randevusu, kural değişirse yanlış saate düşer.

Çözüm: geleceğe dönük planlarda yerel saati ve dilim adını ayrı alanlarda saklayın, UTC'ye çevirmeyi gösterim veya tetikleme anında yapın.

SQL
-- Geçmiş olay: UTC yeter
CREATE TABLE olay (olustu_at DATETIME NOT NULL);         -- UTC

-- Gelecek randevu: yerel saat + dilim adı
CREATE TABLE randevu (
  yerel_zaman  DATETIME    NOT NULL,      -- '2027-03-15 14:00:00'
  zaman_dilimi VARCHAR(64) NOT NULL       -- 'Europe/Istanbul'
);

Dışarıdan gelen tarihler

Üçüncü parti API'lerden gelen tarihlerde dilim bilgisi çoğu zaman vardır ama kolayca göz ardı edilir. 2026-08-14T09:30:00+02:00 değerini dilimini atarak okursanız iki saat kayarsınız. Ayrıştırırken dilim bilgisini koruyan bir yöntem kullanın ve saklamadan önce UTC'ye çevirin. Entegrasyon tarafındaki diğer tuzaklar için dayanıklılık desenleri yazısına bakabilirsiniz.

Hızlı denetim listesi

  • Uygulama, veritabanı bağlantısı ve sunucu aynı dilimde mi (tercihen UTC)?
  • Şablonlarda ham tarih basılan bir yer kaldı mı?
  • Rapor sorgularında gün sınırı kullanıcının diliminde mi hesaplanıyor?
  • BETWEEN ... 23:59:59 kalıbı var mı?
  • Cron işleri UTC'ye göre mi kurulu ve tekrar çalıştırılabilir mi?
  • Geleceğe dönük randevular dilim adıyla mı saklanıyor?

Test verinizi her zaman gün sınırına yakın saatlerde üretin. Öğlen 12:00'de oluşturulan test kayıtları hiçbir zaman bu hataları göstermez; gece 01:00'de oluşturulanlar ilk denemede gösterir.

Sonuç

Saat dilimi hataları, üretimde günlerce sessiz kalıp sonra bir muhasebe uyuşmazlığı olarak geri dönen türden. Korunmanın yolu karmaşık bir kütüphane değil, üç satırlık bir kural: UTC sakla, kullanıcının diliminde göster, gün sınırlarını kullanıcının diliminde hesapla. Bu üçünü koda ve kod inceleme şablonuna yazmak, konuyu bir daha gündeme getirmemek için genellikle yeterli oluyor. Hata avlama alışkanlıkları için sık karşılaşılan PHP hataları yazısı da işinizi görür.

Saat dilimi verisinin kaynağı ve güncellenme süreci: IANA Time Zone Database.

Pratik Uygulama Kontrol Listesi

Bu yazıdaki önerileri kendi ekibinize uyarlarken aşağıdaki adımları sırasıyla uygulamanız, teoriden pratiğe geçişi kolaylaştırır:

  1. Mevcut durumu ölçün. Değişiklik yapmadan önce bugünkü performansı/süreyi/hata oranını kaydedin; aksi halde iyileşmeyi kanıtlayamazsınız.
  2. Küçük bir pilot seçin. Tüm sisteme veya tüm ekibe birden uygulamak yerine tek bir modülde veya tek bir sprintte deneyin.
  3. Sonuçları ekiple paylaşın. Elde ettiğiniz veriyi (olumlu ya da olumsuz) kısa bir notla ekibe aktarın; kararın gerekçesi belgelenmemişse aynı tartışma birkaç ay sonra tekrar açılır.
  4. Süreci tekrarlanabilir hale getirin. İşe yarayan pratiği bir kontrol listesine veya şablona dönüştürün ki yeni katılan ekip üyeleri de aynı standardı hızlıca öğrensin.

Saat Dilimi Hataları konusunda attığınız her küçük adım ölçülebilir olduğu sürece değerlidir; büyük ve tek seferlik dönüşümler yerine sürekli, küçük iyileştirmeler uzun vadede daha kalıcı sonuç verir.

Sık Yapılan Yanlışlar

Bu konuda ekiplerin en çok düştüğü tuzak, çözümü tek bir kişiye ya da tek bir araca yüklemektir. Oysa kalıcı iyileşme, sürecin ekibin günlük rutinine (code review kontrol listesi, sprint planlama, onboarding dokümanı gibi) gömülmesiyle mümkün olur. İkinci yaygın hata ise "en iyi pratiği" olduğu gibi kopyalamaktır — başka bir ekipte işe yarayan bir yaklaşım, farklı bir ölçekte veya farklı bir teknoloji yığınında aynı sonucu vermeyebilir; önce kendi bağlamınızda küçük ölçekte test edin, sonra genişletin. Üçüncü tuzak ise ölçmeden karar vermektir: "daha iyi hissettiriyor" öznel bir gerekçedir, ekip içi tartışmalarda nesnel veriyle desteklenmeyen kararlar er ya da geç sorgulanır ve geri alınır. Son olarak, dokümantasyonu atlamak da sık görülen bir hatadır: bir kararın "neden" alındığı yazılı değilse, ekip altı ay sonra aynı tartışmayı sıfırdan yeniden yapmak zorunda kalır ve önceki deneyimden öğrenilenler kaybolur.

Sonuç

Saat Dilimi Hatalarından Kurtulmak: UTC Disiplini konusunda burada değindiğimiz noktalar, konuyu ilk kez ele alan ekipler için de deneyimli geliştiriciler için de pratik bir kontrol listesi görevi görür. saat dilimi hataları üzerine çalışırken en çok fayda sağlayan yaklaşım, tek seferde her şeyi mükemmelleştirmeye çalışmak yerine küçük, ölçülebilir adımlarla ilerlemektir. Ekibinizde bu konuyu bir sonraki sprint retrospektifinde veya teknik tartışma toplantısında gündeme getirmenizi, burada anlatılan pratiklerden hangilerinin sizin bağlamınıza uyduğunu birlikte değerlendirmenizi öneririz. Sonuç olarak, doğru araçları seçmek kadar bunları ekip kültürüne oturtmak da başarıyı belirleyen asıl etkendir.

Beğeniniz benzer içerikleri öne çıkarmamıza yardımcı olur.

Sık Sorulan Sorular

Üç yerde: günlük/aylık raporlarda (gün sınırı kayar), zamanlanmış işlerde (yaz saati geçişinde bir iş atlanır veya iki kez çalışır) ve bildirim gönderiminde (kullanıcının gecesine denk gelir). Üçü de sessizce yanlış çalışır; istisna fırlatmaz, bu yüzden geç fark edilir.

MySQL'de TIMESTAMP, oturumun saat dilimine göre otomatik dönüşüm yapar; bu, sunucu ayarı değiştiğinde verinizin anlamını değiştirir. Öngörülebilirlik için DATETIME kullanıp değeri her zaman UTC olarak yazmak daha güvenli. Önemli olan tipten çok tutarlılık: tüm uygulamada tek bir kural olmalı.

Çünkü bir ülkenin saat dilimi kuralları değişebiliyor. Bugünkü kurala göre UTC'ye çevirdiğiniz "15 Mart saat 14:00" randevusu, kural değişirse yanlış saate düşer. Geleceğe dönük randevularda yerel saati ve saat dilimi adını ayrı ayrı saklayın, çevirmeyi gösterim anında yapın.

Evet. Kullanıcılarınızın veya sunucularınızın hepsi aynı ülkede olmayabilir; bulut sağlayıcıları varsayılan olarak UTC kullanır ve dış servislerden gelen tarihler kendi dilimlerinde gelir. Ayrıca kural, yaz saati olmasa bile farklı dilimler arasında geçerliliğini koruyor.

S
superadmin

Bu yazıyı hazırladı. Sorularınız için iletişim sayfasından ulaşabilirsiniz.

Yorumlar (1)

K
kodmeraklisi 19.08.2026 01:10
Veritabanında tarihi UTC tutup ekranda Türkiye saatine çevirebilir miyiz

Yorum Yaz

Yorumunuz onaylandıktan sonra yayınlanır. Ekibimiz gerekirse konuyla ilgili bir yanıt da paylaşır.

En az 10 karakter.