PostgreSQL timestamp ve timestamptz: Kaputun Altında Aslında Ne Saklanır?
PostgreSQL hem timestamp’i hem de timestamptz’i tek bir 64 bitlik tam sayı olarak tutar: 1970-01-01 00:00:00 UTC’den bu yana geçen mikrosaniyelerin sayısı. Ayrım yalnızca veriyi insanların okuyabileceği biçime dönüştürürken ortaya çıkar.
Bu İnsanların Aklını Neden Karıştırır?
- İki sütun, tek bir tarih… iki farklı sorgu sonucu
- Uygulamanız
2025-07-29 10:00ekler, ama başka bir ekip02:00görür - Ön uç, arka uç logundakiyle eşleşmeyen bir ISO string’i oluşturur
İki Şeftali Konservesi: Biri Sade, Biri Etiketli
| Veri Türü | Resmi Adı | Saklanan Değer | SELECT Üzerinde Ne Olur |
|---|---|---|---|
timestamp | timestamp without time zone | ham mikrosaniye sayımı | Değiştirilmeden geri gönderilir — Postgres asla bir saat dilimi tahmin etmez |
timestamptz | timestamp with time zone | aynı mikrosaniye sayımı | Postgres, metni göndermeden hemen önce oturumun TimeZone ayarını uygular |
Benzetme
timestamp= köken etiketi olmayan bir şeftali kavanozu. Meyve olduğunu bilirsiniz, ama nerede konservelendiğini değil.timestamptz= “UTC+8’de Üretildi” yazan, gururla damgalanmış bir kavanoz. Açan herkes, besin değeri panelini dönüştürüp dönüştürmemeye karar verebilir.
Kaputun Altında: Sadece Devasa Bir Sayı
2000-01-01 00:00:00 UTC → 0
2000-01-01 00:00:01 UTC → 1 000 000
- Birim: mikrosaniye (saniyenin milyonda biri)
- Aralık: MÖ 4713 – MS 294276 — Indiana Jones onaylı
timestampvetimestamptziçin depolama birebir aynıdır; yorumlama farklıdır
15 Saniyelik Bir Demo
-- İstemci Şangay saatinde düşünüyor
SET TimeZone = 'Asia/Shanghai';
CREATE TABLE demo (
created_ts timestamp,
created_tz timestamptz
);
INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
| Sorgu | Sonuç | Neden |
|---|---|---|
SELECT created_ts FROM demo; | 2025-07-29 10:00:00 | Ham değer, TZ aritmetiği yok |
SELECT created_tz FROM demo; | 2025-07-29 10:00:00+08 | Etiket, çıktıda uygulanır |
SET TimeZone = 'UTC'; ardından select | 2025-07-29 02:00:00+00 | Aynı an, yeni mercek |
Zaman Damgası Aritmetiği ve Aralıklar
PostgreSQL zaman damgalarının en pratik yönlerinden biri, aralık (interval) aritmetiğidir. Her iki tür de mikrosaniye sayımları sakladığı için aralıkları doğrudan toplayıp çıkarabilirsiniz:
-- 3 saat 30 dakika ekle
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08
-- İki zaman damgası arasındaki farkı bul
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00 (bir aralık)
-- Belirli alanları çıkar
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800 (saniye cinsinden Unix zaman damgası)
-- Gün sınırına yuvarla (günlük toplamalar için yararlı)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08
EXTRACT(EPOCH FROM ...) fonksiyonu, zaman damgalarını Unix epoch saniyesi bekleyen dış sistemlere geçirmeniz gerektiğinde özellikle yararlıdır. Tersine, bir epoch değerini tekrar zaman damgasına dönüştürebilirsiniz:
SELECT to_timestamp(1753768800);
-- → 2025-07-29 10:00:00+08 (Asia/Shanghai oturumunda)
İnce ama önemli bir nokta: timestamp’in (saat dilimsiz) aralık aritmetiği DST geçişlerini tamamen yok sayarken, timestamptz bunları gözetir. Bu, bir DST sınırını aşan bir timestamptz değerine INTERVAL '1 day' eklemenin, tam olarak 24 saat sonrasını değil — aynı duvar saati saatini doğru biçimde döndüreceği anlamına gelir.
Index’leme ve Performans Hususları
Hem timestamp hem de timestamptz, 8 baytlık tam sayılar olarak saklanır; bu nedenle depolama veya index’leme açısından aralarında performans farkı yoktur. B-tree index’leri her iki tür üzerinde aynı şekilde çalışır, çünkü temel karşılaştırma yalnızca tam sayı karşılaştırmasıdır.
Ne var ki, birkaç pratik husus vardır:
- Aralık sorguları:
WHERE created_at > '2025-07-01'her iki tür üzerindeki bir index’le verimli biçimde çalışır.timestamptzile PostgreSQL, karşılaştırmadan önce sabit değeri UTC’ye dönüştürür, böylece index yine kullanılır. - Bölümleme anahtarları: Zaman damgası sütunlarında aralık bölümlemesi (range partitioning) kullanırken
timestamptzgenellikle daha güvenlidir, çünkü bölüm sınırları belirsizlik içermez (her zaman UTC).timestampile'2025-07-01 00:00'gibi bir sınır, farklı oturumlar için farklı şeyler ifade edebilir. - Fonksiyonel index’ler: Sıklıkla yalnızca tarihe göre (saati yok sayarak) sorgu yapıyorsanız, günlük toplama sorgularını hızlandırmak için
date_trunc('day', created_at)üzerinde bir index düşünün.
Yaygın Tuzaklar ve Hızlı Çözümler
1. Farklı kullanıcılar, farklı saatler
- Sebep: istemciler
timestamptzile farklıTimeZoneayarları kullanır - Çözüm: ya her şeyi
timestamptutup tek bir saat dilimi üzerinde anlaşın, ya da bağlantı başlatma sırasındaSET TimeZone = 'UTC'zorlayın
Uygulama kodunda yaygın bir desen, saat dilimini bağlantı havuzu başlatma sırasında bir kez ayarlamaktır:
-- Bağlantı kurulumunda (ör. pg pool yapılandırması)
SET timezone = 'UTC';
Bu, tüm oturumların aynı UTC temsilini görmesini sağlar; uygulama katmanınız ise gösterim için yerel saate dönüşümü ele alır.
2. “Duvar saati” saklamak ama yanlış türü seçmek
- İş takvimleri (mağaza saatleri, son ödeme tarihleri)
timestampkullanmalıdır - Sınır ötesi iş akışları (siparişler, loglar)
timestamptziçinde UTC saklamalıdır
Test basittir: soru “bu, zamanın hangi anında oldu?” ise timestamptz kullanın. Soru “duvardaki saat ne diyor?” ise timestamp kullanın.
3. Sürüklenen API’ler
timestamptz’i her zaman offset (Zveya+08:00) içeren ISO 8601 string’leri olarak gönderin- Biçimlendirmeyi yerel olarak UI’a bırakın
4. Türler arası zaman damgası karşılaştırması
Karşılaştırmalarda veya birleştirmelerde (join) timestamp ile timestamptz’i karıştırmak, ince hataların yaygın bir kaynağıdır:
-- Tehlikeli: örtük dönüşüm oturum saat dilimini uygular
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- PostgreSQL, oturum saat dilimini kullanarak s.start_ts'i timestamptz'e dönüştürür
-- Farklı oturumlar farklı join sonuçları alabilir!
Çözüm: türler arası karşılaştırma yaparken her zaman açıkça cast yapın veya etki alanı başına tek bir tür üzerinde standartlaşın.
5. ORM varsayılan tuzakları
Birçok ORM (Django, SQLAlchemy, ActiveRecord) varsayılan olarak saat dilimsiz timestamp kullanır. Şema taşıması (migration) dosyalarınızı denetleyin — uygulamanız saat dilimleri arasındaki kullanıcılara hizmet veriyorsa varsayılanı timestamptz’e geçirin. Django’da ayarlarda USE_TZ = True belirleyin. SQLAlchemy’de DateTime(timezone=True) kullanın.
Hile Sayfası: Hangisini Kullanmalıyım?
Yalnızca yerel takvim → timestamp
Küresel olan her şey → timestamptz (UTC saklayın)
- Finansal raporlar, ders programları →
timestamp - Denetim logları, e-ticaret siparişleri →
timestamptz
Go Tools ile Saniyeler İçinde Doğrulayın
| İhtiyaç | Araç | Nasıl |
|---|---|---|
| SQL’den epoch değerini incelemek | Epoch Dönüştürücü | 1690622400 yapıştırın, Dönüştür’e basın |
| Zaman alanları içeren toplu JSON’u düzenlemek | JSON Biçimlendirici | Yükü bırakın, biçimlendirip tarayın |
Tüm yardımcı araçlar tamamen tarayıcınızda çalışır — hiçbir veri makinenizden ayrılmaz.
Sıkça Sorulan Sorular
PostgreSQL’de timestamp ile timestamptz arasındaki fark nedir?
timestamp (saat dilimsiz), bir tarih-saat değerini olduğu gibi, hiçbir saat dilimi bağlamı olmadan saklar. timestamptz (saat dilimli), girdiyi depolama için UTC’ye dönüştürür ve veriyi alırken oturumun saat dilimine geri dönüştürür. Neredeyse her durum için timestamptz kullanın — dağıtık sistemler genelinde saat dilimi kaynaklı hataları önler.
PostgreSQL gerçekten saat dilimini timestamptz içinde saklar mı?
Hayır — adına rağmen, PostgreSQL saat diliminin kendisini saklamaz. Girdiyi UTC’ye dönüştürür ve yalnızca UTC değerini saklar (2000-01-01’den itibaren bir mikrosaniye sayımı). Veri alımı sırasında, UTC’den oturumunuzun timezone ayarının belirttiği saat dilimine dönüştürür. Orijinal saat dilimi bilgisi atılır.
PostgreSQL oturumunun saat dilimini nasıl değiştiririm?
Oturum saat dilimini değiştirmek için SET timezone = 'America/New_York'; çalıştırın. Bu, timestamptz değerlerinin nasıl gösterileceğini ve yorumlanacağını etkiler. Sunucu genelindeki varsayılanlar için postgresql.conf içinde timezone ayarlayın. Belirsizlikten kaçınmak için kısaltmalar (CST gibi) yerine her zaman IANA saat dilimi adlarını (Asia/Shanghai gibi) kullanın.
Olay zamanlarını saklamak için timestamp mi yoksa timestamptz mi kullanmalıyım?
Neredeyse her şey için timestamptz kullanın — kullanıcı eylemleri, API çağrıları, denetim logları ve zamanlanmış olaylar. timestamp’i (saat dilimsiz) yalnızca belirli bir ana bağlı olmayan soyut zamanlar için kullanın; örneğin “mağaza 09:00’da açılır” — burada kastedilen, belirli bir UTC anı değil, hangi yerel saat dilimiyse oradaki sabah 9’dur.
PostgreSQL, timestamptz ile yaz saati uygulamasını nasıl ele alır?
PostgreSQL, timestamptz kullanırken DST’yi doğru biçimde ele alır, çünkü her şeyi dahili olarak UTC içinde saklar. Bir değer aldığınızda, PostgreSQL UTC’den oturum saat diliminizin geçerli DST kurallarını kullanarak dönüştürür. Bu, aynı saklanan UTC anının, bir DST geçişinden önce ve sonra farklı yerel saatleri doğru biçimde göstereceği anlamına gelir.
Unix zaman damgalarına ilişkin kapsamlı bir rehber için — hassasiyet yönetimi, saat dilimi iyi pratikleri ve JavaScript, Python ve Go’daki kod örnekleri dahil — Unix Zaman Damgası Rehberi’mize bakın.
Toparlayalım
- Her iki Postgres zaman türü de mikrosaniye sayaçlarıdır; bütün fark etikettedir
- Yanlış olanı seçmek, kafa karıştırıcı zaman damgaları ve bozuk aritmetik anlamına gelir
- Saatlerce hata ayıklamadan kurtulmak için doğru araçlarla test edin, dönüştürün ve sağduyu denetimi yapın