Python'da tarih ve saat işlemleri için standart datetime modülü kullanılır. date yalnızca gün, ay ve yılı; time günün saatini; datetime ikisini birlikte; timedelta ise iki an arasındaki süreyi tutar. Metne çevirmek için strftime(), metinden okumak için strptime() ya da ISO 8601 biçimi için fromisoformat() kullanılır. Saat dilimi gerektiğinde datetime.UTC, sabit farklar için timezone ve gerçek bölge kuralları için zoneinfo.ZoneInfo devreye girer.
Bu yazıda nesneleri oluşturmayı, tarih aritmetiğini, biçimlendirme kodlarını, “naive” ve “aware” nesneler arasındaki kritik farkı, Python 3.15'te strptime() davranışında yapılan değişikliği ve kullanımdan kaldırılan utcnow() yerine ne yazmanız gerektiğini göreceksiniz. Örnekler Python 3.15.2 ile çalıştırıldı; zoneinfo örneği ise saat dilimi verisi gerektirdiği için aşağıda açıklandığı gibi denendi. Konuların genel sıralaması için Python öğrenme rehberi sayfasına bakabilirsiniz.
Modülün tipleri ve ilk nesneler

Belgeler modülü içe aktarırken import datetime as dt yazımını önerir. Bunun nedeni modülün ve içindeki sınıfın aynı adı taşımasıdır: from datetime import datetime yazdığınızda kodun ilerisinde datetime adının modülü mü sınıfı mı gösterdiği karışabilir. Bu yazıdaki örnekler belgelerin önerisine uyar. Modüldeki tüm nesneler değiştirilemezdir (immutable); replace() mevcut nesneyi değiştirmez, yeni bir nesne döndürür.
import datetime as dt
dogum = dt.date(1990, 4, 23)
toplanti = dt.datetime(2026, 11, 7, 14, 30)
saat = dt.time(9, 15)
print(dogum, toplanti, saat)
# çıktı: 1990-04-23 2026-11-07 14:30:00 09:15:00
print(repr(toplanti))
# çıktı: datetime.datetime(2026, 11, 7, 14, 30)
print(dogum.year, dogum.month, dogum.day, dogum.weekday(), dogum.isoweekday())
# çıktı: 1990 4 23 0 1
print(dt.datetime.combine(dogum, saat))
# çıktı: 1990-04-23 09:15:00
print(toplanti.date(), toplanti.time())
# çıktı: 2026-11-07 14:30:00
yeni = toplanti.replace(hour=16, minute=0)
print(yeni, toplanti)
# çıktı: 2026-11-07 16:00:00 2026-11-07 14:30:00weekday() pazartesiyi 0, pazarı 6 olarak verir; isoweekday() ise ISO 8601'e uygun olarak pazartesiyi 1, pazarı 7 sayar. Haftanın gününü bir listeden Türkçe ada çevirecekseniz hangisini kullandığınıza dikkat edin; bir kaydırma hatası her günü bir sonraki günün adıyla gösterir. Geçerli an için dt.datetime.now(), bugünün tarihi için dt.date.today() kullanılır. Belgeler now() metodunu today() ve utcnow() yerine tercih edilecek yöntem olarak belirtir.
| Tip | Ne tutar | Örnek |
|---|---|---|
| date | yıl, ay, gün | dt.date(2026, 11, 7) |
| time | saat, dakika, saniye, mikrosaniye, tzinfo | dt.time(9, 15) |
| datetime | date ve time bilgisinin tamamı | dt.datetime(2026, 11, 7, 14, 30) |
| timedelta | süre (gün, saniye, mikrosaniye) | dt.timedelta(days=3) |
| timezone | UTC'ye göre sabit fark | dt.timezone(dt.timedelta(hours=3)) |
| tzinfo | saat dilimi için soyut temel sınıf | ZoneInfo bu sınıftan türer |
timedelta ile tarih aritmetiği
İki date ya da iki datetime nesnesini birbirinden çıkardığınızda bir timedelta elde edersiniz. Bir tarihe timedelta eklemek ya da çıkarmak yeni bir tarih verir. timedelta içeride yalnızca üç değer saklar: gün, saniye ve mikrosaniye. Hafta, saat, dakika ve milisaniye cinsinden verdiğiniz değerler bu üç birime çevrilip normalize edilir. Bu yüzden seconds özniteliği hiçbir zaman bir günden uzun olamaz; belgeler, toplam süreyi isterken yanlışlıkla seconds özniteliğini okumanın yaygın bir hata olduğunu özellikle belirtir. Toplam saniye için total_seconds() kullanın.
import datetime as dt
baslangic = dt.date(2026, 11, 7)
print(baslangic + dt.timedelta(days=30))
# çıktı: 2026-12-07
print(baslangic - dt.date(2026, 1, 1))
# çıktı: 310 days, 0:00:00
fark = dt.datetime(2026, 11, 7, 18, 0) - dt.datetime(2026, 11, 5, 9, 30)
print(fark, fark.days, fark.seconds, fark.total_seconds())
# çıktı: 2 days, 8:30:00 2 30600 203400.0
print(fark / dt.timedelta(hours=1))
# çıktı: 56.5
print(dt.timedelta(weeks=2, hours=36))
# çıktı: 15 days, 12:00:00
d = dt.timedelta(hours=-1)
print(d, (d.days, d.seconds))
# çıktı: -1 day, 23:00:00 (-1, 82800)Bir timedelta nesnesini başka bir timedelta nesnesine bölmek bir sayı verir; “kaç saat sürdü?” sorusunun en temiz cevabı fark / dt.timedelta(hours=1) ifadesidir. Negatif sürelerin gösterimi ilk bakışta şaşırtıcıdır: eksi bir saat, “eksi bir gün artı 23 saat” olarak saklanır ve yazdırılır. Bu yanlış değildir, normalize edilmiş biçimdir. Kullanıcıya gösterecekseniz, belgelerdeki tarife benzer şekilde önce işaretini kontrol edip mutlak değeri yazdırabilirsiniz.
strftime ve strptime: biçimlendirme kodları
strftime(format) bir nesneyi verdiğiniz kalıba göre metne çevirir; strptime(metin, format) ise tersini yapar ve bir sınıf metodudur. Kalıptaki % ile başlayan kodlar tarih parçalarını temsil eder. f-string içinde de aynı kodları {an:%d.%m.%Y} biçiminde kullanabilirsiniz; bu, nesnenin __format__ metodu sayesinde çalışır. Biçimlendirme mini dili hakkında daha fazlası f-string yazısında anlatılıyor.
Aşağıdaki tablonun örnek sütunu, bir sonraki koddaki dt.datetime(2026, 3, 5, 8, 7, 9) nesnesi için Python 3.15.2'nin ürettiği değerlerdir.
| Kod | Anlamı | Örnek |
|---|---|---|
| %d | ayın günü, iki hane | 05 |
| %m | ay, iki hane | 03 |
| %Y | yıl, dört hane | 2026 |
| %y | yıl, iki hane | 26 |
| %H | saat, 24 saat düzeni | 08 |
| %I %p | saat, 12 saat düzeni ve AM/PM | 08 AM |
| %M | dakika | 07 |
| %S | saniye | 09 |
| %f | mikrosaniye, altı hane | 000000 |
| %j | yılın günü, üç hane | 064 |
| %A %a | haftanın günü adı (yerel ayara bağlı) | Thursday Thu |
| %B %b | ay adı (yerel ayara bağlı) | March Mar |
| %z | UTC farkı (naive nesnede boş metin) | +03:00 dilimli nesnede +0300 |
import datetime as dt
an = dt.datetime(2026, 3, 5, 8, 7, 9)
print(an.strftime("%d.%m.%Y %H:%M"))
# çıktı: 05.03.2026 08:07
print(an.strftime("%Y-%m-%d"), an.strftime("%j"), an.strftime("%A %B"))
# çıktı: 2026-03-05 064 Thursday March
print(f"{an:%d/%m/%Y}")
# çıktı: 05/03/2026
okunan = dt.datetime.strptime("07.11.2026 14:30", "%d.%m.%Y %H:%M")
print(repr(okunan))
# çıktı: datetime.datetime(2026, 11, 7, 14, 30)
try:
dt.datetime.strptime("2026-11-07", "%d.%m.%Y")
except ValueError as e:
print(e)
# çıktı: time data '2026-11-07' does not match format '%d.%m.%Y'
print(repr(dt.date.strptime("07.11.2026", "%d.%m.%Y")))
# çıktı: datetime.date(2026, 11, 7)
print(repr(dt.time.strptime("14:30", "%H:%M")))
# çıktı: datetime.time(14, 30)%A ve %B çıktısının İngilizce olmasının nedeni, belgelerin de belirttiği gibi bu kodların yerel ayara (locale) bağlı olmasıdır. Programın yerel ayarı değiştirilmediği sürece İngilizce adlar gelir. Türkçe gün ve ay adlarını güvenilir biçimde göstermenin basit yolu, kendi listenizi tutup weekday() ve month değerleriyle indekslemektir; böylece sunucunun yerel ayarına bağımlı olmazsınız. date.strptime() ve time.strptime() sınıf metotları Python 3.14'te eklendi; daha önce yalnızca datetime.strptime() vardı ve sonucu .date() ile kesmeniz gerekiyordu.
Python 3.15: yılsız %d artık hata veriyor
Python 3.15'te önemli bir davranış değişikliği var. Biçim metninde %d (ayın günü) bulunup yıl kodu bulunmadığında strptime() artık ValueError fırlatıyor. What's New belgesine göre bu kullanım 3.13'ten beri kullanımdan kaldırılmış durumdaydı. Nedeni şu: yıl verilmediğinde varsayılan olarak kullanılan yıl artık yıl değildir; bu yüzden “29.02” gibi bir metin dört yılda bir patlayan sinsi bir hataya yol açar. Belgelerdeki çözüm, kalıba her zaman bir yıl eklemek, yılı olmayan verilerde de ayrıştırmadan önce metne artık yıl olan bir yıl eklemektir.
import datetime as dt
try:
dt.datetime.strptime("29.02", "%d.%m")
except ValueError as e:
print(type(e).__name__ + ":", e)
# çıktı: ValueError: Day of month directive '%d' may not be used without a year directive. Parsing dates involving a day of month without a year is ambiguous and fails to parse leap day. Add a year to the input and format. See https://github.com/python/cpython/issues/70647.
gun = dt.datetime.strptime("29.02.2024", "%d.%m.%Y")
print(gun.strftime("%d.%m"))
# çıktı: 29.02Aynı kural için ikinci bir adım da var: yılsız %e kodu 3.15'ten itibaren kullanımdan kaldırılmış durumda ve 3.17'de kaldırılması planlanıyor. Doğum günü ya da yıllık tekrar eden olaylar gibi yalnızca gün ve ay içeren verileri işliyorsanız, kodunuzu 3.15'e geçirmeden önce bu kalıpları gözden geçirin. Sürümdeki diğer değişiklikler için Python 3.15 yenilikleri yazısına bakabilirsiniz.
ISO 8601: fromisoformat ve isoformat
Makineler arası veri alışverişinde en güvenli biçim ISO 8601'dir: 2026-11-07T14:30:00+03:00. isoformat() bu biçimi üretir, fromisoformat() okur. Python 3.11'den önce fromisoformat() yalnızca isoformat() metodunun ürettiği biçimleri okuyabiliyordu; 3.11'den beri geçerli ISO 8601 biçimlerinin çoğunu kabul ediyor. Z son ekini UTC olarak tanır ve 20261107 gibi ayraçsız yazımı da okur. Belgeler bazı istisnaları sayar: yalnızca yıl ya da yıl-ay gibi azaltılmış hassasiyetli tarihler (2026-11), genişletilmiş yıl gösterimi ve yılın gününe dayalı (ordinal) tarihler desteklenmez.
import datetime as dt
print(repr(dt.datetime.fromisoformat("2026-11-07T14:30:00+03:00")))
# çıktı: datetime.datetime(2026, 11, 7, 14, 30, tzinfo=datetime.timezone(datetime.timedelta(seconds=10800)))
print(repr(dt.datetime.fromisoformat("2026-11-07T11:30:00Z")))
# çıktı: datetime.datetime(2026, 11, 7, 11, 30, tzinfo=datetime.timezone.utc)
print(repr(dt.date.fromisoformat("20261107")))
# çıktı: datetime.date(2026, 11, 7)
try:
dt.date.fromisoformat("2026-11")
except ValueError as e:
print(e)
# çıktı: Invalid isoformat string: '2026-11'
an = dt.datetime(2026, 11, 7, 14, 30, 5, 123456)
print(an.isoformat())
# çıktı: 2026-11-07T14:30:05.123456
print(an.isoformat(sep=" ", timespec="minutes"))
# çıktı: 2026-11-07 14:30
print(an.isoformat(timespec="milliseconds"))
# çıktı: 2026-11-07T14:30:05.123timespec parametresi saatin hangi hassasiyetle yazılacağını belirler: 'hours', 'minutes', 'seconds', 'milliseconds' ya da 'microseconds'. Belgeler, dışarıda bırakılan parçaların yuvarlanmadığını, kesildiğini vurgular; yukarıdaki 123456 mikrosaniye 123 milisaniye olarak yazıldı. Tarihleri JSON'a ya da veritabanına yazarken ISO biçimini kullanmak, okurken herhangi bir kalıp yazmadan fromisoformat() ile geri dönmenizi sağlar. JSON tarafı için json modülü yazısına bakabilirsiniz.
Naive ve aware: saat dilimi meselesi

Belgelerin en önemli kavramlarından biri budur. Saat dilimi bilgisi taşımayan bir datetime “naive” (saf) nesnedir; bu nesnenin UTC'yi mi, yerel saati mi yoksa başka bir bölgeyi mi gösterdiği tamamen programın yorumuna kalmıştır. Belgeler bunu bir sayının metre mi yoksa mil mi olduğunun programa bağlı olmasına benzetir. tzinfo özniteliği dolu olan ve utcoffset() değeri None olmayan nesne ise “aware” (bilinçli) nesnedir ve zaman çizgisinde tek bir anı gösterir. date nesneleri her zaman naive'dir.
İki tür nesne birbirine hiçbir zaman eşit sayılmaz ve sıralama karşılaştırması yapıldığında TypeError verir. Bu hata çoğu zaman bir kaynaktan gelen tarihin dilimli, diğerinin dilimsiz olmasından kaynaklanır. Belgeler ayrıca dikkat çekici bir öneri yapar: naive nesneler birçok metotta yerel saat gibi ele alındığı için, UTC zamanını göstermek istediğinizde aware nesne kullanmanız tercih edilir.
import datetime as dt
utc_simdi = dt.datetime(2026, 11, 7, 11, 30, tzinfo=dt.UTC)
tr = dt.timezone(dt.timedelta(hours=3), "TRT")
print(utc_simdi)
# çıktı: 2026-11-07 11:30:00+00:00
print(utc_simdi.astimezone(tr), utc_simdi.astimezone(tr).tzname())
# çıktı: 2026-11-07 14:30:00+03:00 TRT
yerel = dt.datetime(2026, 11, 7, 14, 30)
print(yerel.tzinfo, yerel.utcoffset())
# çıktı: None None
print(yerel.replace(tzinfo=tr) == utc_simdi)
# çıktı: True
print(yerel == utc_simdi)
# çıktı: False
try:
yerel < utc_simdi
except TypeError as e:
print(e)
# çıktı: can't compare offset-naive and offset-aware datetimes
print(utc_simdi.timestamp())
# çıktı: 1794051000.0
print(dt.datetime.fromtimestamp(1794051000, tz=dt.UTC))
# çıktı: 2026-11-07 11:30:00+00:00İki metodu karıştırmamak gerekir. astimezone(tz) aynı anı başka bir dilimde ifade eder; saat değeri değişir, an değişmez. replace(tzinfo=tz) ise saat değerine dokunmadan nesneye dilim bilgisi yapıştırır; yani gösterilen anı değiştirir. Elinizde “bu saat İstanbul saatidir” bilgisiyle gelen naive bir değer varsa replace(), elinizde zaten doğru bir aware an varsa ve onu başka bölgede göstermek istiyorsanız astimezone() doğru seçimdir. timestamp() aware nesnelerde 1970-01-01 UTC'den beri geçen saniyeyi verir; naive nesneleri ise yerel saat kabul eder.
| İşlem | Saat değeri | Gösterilen an |
|---|---|---|
| astimezone(tz) | değişir | aynı kalır |
| replace(tzinfo=tz) | aynı kalır | değişir |
| replace(tzinfo=None) | aynı kalır | dilim bilgisi silinir, nesne naive olur |
utcnow() yerine now(dt.UTC)
datetime.utcnow() ve datetime.utcfromtimestamp() Python 3.12'den beri kullanımdan kaldırılmış durumdadır. İkisi de UTC zamanını naive bir nesne olarak döndürür; bu nesne daha sonra yerel saat gibi yorumlanabildiği için hata kaynağıdır. 3.15.2'de utcnow() çağrısı, uyarılar görünür kılındığında DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version mesajını verir ve yerine datetime.datetime.now(datetime.UTC) kullanmanızı önerir. Kullanımdan kaldırılanlar listesinde de aynı karşılıklar yazılıdır.
import datetime as dt
simdi_utc = dt.datetime.now(dt.UTC) # utcnow() yerine
ts_utc = dt.datetime.fromtimestamp(0, tz=dt.UTC) # utcfromtimestamp(0) yerine
print(simdi_utc.tzinfo, ts_utc)
# çıktı: UTC 1970-01-01 00:00:00+00:00dt.UTC sabiti Python 3.11'de eklendi ve dt.timezone.utc ile aynı nesnenin diğer adıdır. Uygulamalarda yaygın ve sağlam bir düzen şudur: zamanı UTC olarak aware nesneyle üretin ve saklayın, yalnızca kullanıcıya gösterirken onun saat dilimine çevirin. Zamanlanmış görevlerde sunucu saatinin hangi dilimde olduğu da önemlidir; bu konu crontab rehberinde ele alınıyor.
zoneinfo ile gerçek saat dilimleri
timezone sınıfı yalnızca sabit bir fark tutar. Yaz saati uygulaması olan ya da tarih boyunca kuralları değişmiş bölgeler için Python 3.9'da eklenen zoneinfo modülü kullanılır. ZoneInfo("Europe/Istanbul") gibi IANA anahtarlarıyla çalışır ve yaz saati geçişlerini kendisi uygular. Modül saat dilimi verisini içinde taşımaz: önce işletim sisteminin veritabanına, bulamazsa PyPI'daki birinci taraf tzdata paketine bakar. Belgeler Windows gibi bazı sistemlerde IANA veritabanının bulunmadığını belirtir ve platformlar arası çalışacak projelerin tzdata paketini bağımlılık olarak eklemesini önerir. İkisi de yoksa ZoneInfoNotFoundError alırsınız.
Bu yazının örneklerini çalıştırdığımız Windows embeddable Python 3.15.2 paketinde tzdata yoktur ve ZoneInfo("Europe/Istanbul") çağrısı tam olarak bu hatayı verdi: ZoneInfoNotFoundError: 'No time zone found with key Europe/Istanbul'. Aşağıdaki örnek bu yüzden tzdata bulunan yerel Python 3.12.7 kurulumunda çalıştırıldı. 3.13, 3.14 ve 3.15 sürüm notlarında zoneinfo için bu örneği etkileyen bir değişiklik yer almıyor.
import datetime as dt
from zoneinfo import ZoneInfo
ist = ZoneInfo("Europe/Istanbul")
berlin = ZoneInfo("Europe/Berlin")
toplanti = dt.datetime(2026, 11, 7, 14, 30, tzinfo=ist)
print(toplanti, toplanti.tzname())
# çıktı: 2026-11-07 14:30:00+03:00 +03
print(toplanti.astimezone(berlin), toplanti.astimezone(berlin).tzname())
# çıktı: 2026-11-07 12:30:00+01:00 CET
yaz = dt.datetime(2026, 7, 1, 12, tzinfo=berlin)
print(yaz, yaz.tzname())
# çıktı: 2026-07-01 12:00:00+02:00 CEST
gece = dt.datetime(2026, 3, 28, 12, tzinfo=berlin)
print(gece + dt.timedelta(days=1))
# çıktı: 2026-03-29 12:00:00+02:00Son satır önemli bir ayrıntıyı gösteriyor. Berlin'de 29 Mart 2026'da yaz saatine geçiliyor. Aynı dilimdeki bir nesneye bir gün eklediğinizde Python duvar saatinde (wall clock) bir gün ilerler: saat yine 12:00'dir ama fark +01:00'dan +02:00'ye geçmiştir. Yani gerçekte geçen süre 23 saattir. Kesin geçen süreyi hesaplamak istiyorsanız işlemi UTC'de yapıp sonra yerel dilime çevirin. Belirsiz saatler (yaz saatinden çıkarken bir saatin iki kez yaşanması) için zoneinfo, PEP 495 ile gelen fold özniteliğini destekler: fold=0 geçişten önceki, fold=1 sonraki farkı seçer.
Sık yapılan hatalar

from datetime import datetimeileimport datetimeyazımlarını aynı dosyada karıştırmak. Belgelerin önerdiğiimport datetime as dtbu karışıklığı önler.- Toplam süre için
timedelta.secondsokumak. Günler bu değere dahil değildir;total_seconds()kullanın. - Naive ve aware nesneleri karşılaştırmak. Kaynağınız ne olursa olsun, hesaplamadan önce hepsini aynı türe getirin.
utcnow()kullanmaya devam etmek. Yerinenow(dt.UTC)yazın.replace(tzinfo=...)ile dilim çevirdiğini sanmak. Çevirme işiastimezone()metodundadır.- Yılı olmayan kalıplarla
strptime()çağırmak. 3.15'te%dile bu artık doğrudan hata verir. %Ave%Bçıktısının Türkçe olmasını beklemek. Bu kodlar yerel ayara bağlıdır.
Sık Sorulan Sorular
Şu anki tarihi ve saati nasıl alırım?
Yerel saat için dt.datetime.now(), UTC için dt.datetime.now(dt.UTC), yalnızca bugünün tarihi için dt.date.today() kullanın. Kalıcı olarak saklayacağınız zaman damgalarında UTC ve aware nesne tercih edin.
Türkçe tarih biçimi (gg.aa.yyyy) nasıl yazılır ve okunur?
Yazmak için an.strftime("%d.%m.%Y"), okumak için dt.datetime.strptime(metin, "%d.%m.%Y") kullanılır. Yalnızca tarih gerekiyorsa 3.14'ten beri dt.date.strptime() doğrudan bir date döndürür.
İki tarih arasındaki gün sayısını nasıl bulurum?
İki date nesnesini çıkarın ve sonucun days özniteliğini okuyun: (bitis - baslangic).days. Saat de varsa ve kesirli değer istiyorsanız fark / dt.timedelta(days=1) yazın.
Tarihleri SQLite'ta nasıl saklamalıyım?
sqlite3 modülünün varsayılan tarih dönüştürücüleri 3.12'den beri kullanımdan kaldırılmış durumdadır. ISO 8601 metni olarak saklamak ve okurken fromisoformat() ile çevirmek basit ve taşınabilir bir yoldur; ayrıntılar sqlite3 yazısında.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Python öğrenme rehberi · json modülü · sqlite3 veritabanı · f-string biçimlendirme · Python 3.15 yenilikleri · csv modülü · crontab zamanlanmış görevler