İçeriğe geç
academia.sh

Ders 09 / 12

Zaman İşlemleri

Bir anın metne dönüp geri gelmesi ölçülür: an kendisi Equal ile korunuyor, ama sayısal saat dilimi ofseti geri kurulsa bile bölgenin adı taşınmıyor. time.Duration'ın bir sayı değil kendi tipiyle taşınan bir değer olduğu gösterilir. Sınırlayıcı ölçüm, aynı anı gösteren iki değerin Equal ile eşit çıkarken == ve String() ile farklı çıktığını koşturarak ortaya koyuyor.

İçindekiler

Önceki ders bir bağın koşum zamanına değil derleme zamanına kurulabileceğini gösterdi: bir kaynak dosyanın içeriği, programın kendisiyle birlikte donuyordu. Zaman bunun tam tersi bir örnek sunuyor — hiçbir değer, “an” kadar koşumun kendisine bağlı. Bir an, ölçüldüğü ânın kendisiyle tanımlanıyor; onu bir metne yazıp geri okumak, önceki iki derste ölçülen kayıplardan farklı bir tür kayba yol açıyor.

Bu farkın somut sonucu şu: dosya ve arabellek dersinde sınırı geçen veri hiçbir zaman tip bilgisi taşımıyordu — kayıp her defasında tamdı. Zaman değerinde durum daha ince: sınırı geçen metin bazı bilgiyi taşıyor, bazısını taşımıyor, ve hangisinin hangi kategoriye girdiği kullanılan yazım düzenine bağlı. Bu ders üç ayrı değeri (an, saat dilimi, süre) aynı “metne dönüp geri gelme” sınavından geçirip her birinin ne kadarının hayatta kaldığını ayrı ayrı ölçüyor.

Belirlenimcilik notu: bu derste gerçek saat hiç okunmuyor (time.Now hiçbir yerde çağrılmıyor). Bütün örnekler sabit bir metinden ayrıştırılan tek bir ana dayanıyor; karşılaştırmalar da bu sabit anın üzerine kuruluyor.

Sabit Bir Andan Başlamak

Go’nun standart kitaplığında bir anı temsil eden tip time.Time; time.Parse bir dizgiyi belirli bir düzene (layout) göre okuyup bir time.Time değeri üretiyor. Bu derste kullanılan düzen RFC 3339 — ağ ve dosya biçimlerinde yaygın kullanılan, saat dilimini de taşıyan bir zaman yazımı.

// sabit-an.go — gercek saat okunmuyor, an sabit bir metinden ayristiriliyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, err := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	if err != nil {
		panic(err)
	}
	fmt.Printf("ayristirilan deger tipi=%T\n", sabitAn)
	fmt.Println("saat dilimi (Z = UTC)  :", sabitAn.Location())
}
ayristirilan deger tipi=time.Time
saat dilimi (Z = UTC)  : UTC

Dizginin sonundaki Z harfi, RFC 3339 düzeninde “UTC, sıfır ofset” anlamına geliyor; time.Parse bunu tanıyıp sonucu doğrudan time.UTC konumuna bağlıyor. Bu ders boyunca kullanılan tek “gerçek” veri bu sabit dizgi; ondan sonraki her adım bu değerden türetiliyor. time.Parsein ikinci argümanı ayrıştırılacak metin, ilk argümanı ise beklenen düzen — metnin kendisi değil, metnin biçiminin bir örneği. Bu örnek yalnızca yer tutucu değil, gerçek bir tarih: time.RFC3339 sabiti Go’nun standart kitaplığında 2006-01-02T15:04:05Z07:00 biçiminde tanımlı, ve bu belirli tarih (ay/gün/saat/dakika/saniye/yıl’ın Go’nun kendi iç sıralamasında karşılık gelen rakamları) düzeni belirtmenin standart yolu; başka bir dilin YYYY-MM-DD gibi soyut yer tutucularından farklı bir yaklaşım.

Bir time.Time değeri her zaman aynı türden veri taşımıyor. Yalnızca time.Now() çağrısından gelen değerler, saat farkı ölçümlerinde kullanılan bir tek yönlü sayaç okuması (duvar saatinden ayrı, yalnızca ileri akan bir sayım) taşıyor; time.Parse gibi bir metinden türeyen değerler bu ek okumayı hiç taşımıyor, yalnızca düz bir duvar saati değeri oluyor. Bu ders yalnızca time.Parse kullandığından, ölçülen her değer bu ikinci, “tek düze” türden.

Zaman Metne Dönüp Geri Geldiğinde

SS8. — Sabit an Format ile metne çevrilip aynı düzenle geri Parse ediliyor. Bu, Ağ ve Veri Biçimleri konusunun ortak çekirdeğinde de bir satır olarak geçen geçiş (zaman değeri metne ve geri: yeniden kuruluyor); o konunun bağlayıcı 1/5/7 dağılımına burada tek cümleyle atıf yapılıyor, çekirdek yeniden kurulmuyor. Bu ders o satırı doğrulayıp saat dilimine özgü bir ayrıntıyla genişletiyor.

// metne-geri.go — bir an metne yazilip ayni duzenle geri okunuyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")

	metin := sabitAn.Format(time.RFC3339)
	geri, _ := time.Parse(time.RFC3339, metin)

	fmt.Println("metne donen yazim :", metin)
	fmt.Println("ayni an mi (Equal):", geri.Equal(sabitAn))
	fmt.Printf("geri gelen tip    : %T\n", geri)
}
metne donen yazim : 2026-08-23T10:00:00Z
ayni an mi (Equal): true
geri gelen tip    : time.Time

Bir time.Time değeri bir dizgiye yazıldığında tip bilgisi tamamen kayboluyor — sonuç sıradan bir metin, içinde “bu bir zaman” diyen hiçbir işaret yok. Ama time.Parse çağıran taraf elindeki dizginin bir zaman olduğunu zaten biliyor ve doğru düzeni vererek onu geri kuruyor; kayan bilgi, çağıranın elindeki bu bilgiyle yeniden kuruluyor. Equal sonucu true: geri kurulan an, orijinal anla birebir aynı. Konunun ölçü ekseninde bu satır “yeniden kuruluyor” sınıfına giriyor — tıpkı bir yapı etiketinin JSON’dan geri kurulması gibi, burada da geri kurmayı sağlayan şey dizginin kendisi değil, çağıranın bildiği düzen.

Saat Dilimi Adı Taşınmıyor

Yukarıdaki ölçüm bir ayrıntıyı gizliyor: sabit an tam olarak UTC’de, yani sıfır ofsette. RFC 3339 düzeni sıfır ofseti Z harfiyle özel olarak işaretlediği için, geri kurulan değer de tam olarak time.UTC’ye bağlanıyor. Sıfırdan farklı bir ofset söz konusu olduğunda tablo değişiyor.

SS9. — Sabit an, adı BolgeA olan (gerçek bir coğrafi bölgeye değil, yalnızca +03:00 ofsetine sahip kurgusal bir saat dilimine) çevriliyor, metne yazılıp geri okunuyor. Ölçüm hem sayısal ofseti hem bölge adını ayrı ayrı karşılaştırıyor.

// bolge-adi.go — sayisal ofset korunuyor, bolgenin adi tasinmiyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	bolgeA := time.FixedZone("BolgeA", 3*3600)
	yerel := sabitAn.In(bolgeA)

	metin := yerel.Format(time.RFC3339)
	geri, _ := time.Parse(time.RFC3339, metin)

	yerelAd, yerelOfset := yerel.Zone()
	geriAd, geriOfset := geri.Zone()

	fmt.Println("metne donen yazim :", metin)
	fmt.Println("ayni an mi (Equal):", yerel.Equal(geri))
	fmt.Println("ofset korunuyor mu:", yerelOfset == geriOfset)
	fmt.Println("bolge adi tasindi mi:", yerelAd == geriAd)
}
metne donen yazim : 2026-08-23T13:00:00+03:00
ayni an mi (Equal): true
ofset korunuyor mu: true
bolge adi tasindi mi: false

RFC 3339 düzeni bir saat diliminin adını hiç taşımıyor, yalnızca sayısal ofsetini (+03:00) yazıyor. Geri okunduğunda time.Parse bu ofseti doğru biçimde geri kuruyor — an hâlâ aynı, Equal hâlâ true, ofset karşılaştırması da true. Ama bölge adı ("BolgeA") hiçbir yerde metne yazılmadığı için geri gelmiyor; Parsein ürettiği yeni konumun adı, orijinal "BolgeA"dan farklı çıkıyor. Bu, kaydın önceki dersle aynı biçimde okunuyor: sayısal ofset yeniden kuruluyor, çünkü metnin kendisi onu taşıyor; bölge adı ise kayboluyor, çünkü metnin kendisi onu hiç taşımıyor. Aynı geçiş, taşınan bilginin parçasına göre iki farklı sınıfa giriyor.

time.Duration Bir Tiptir, Bir Sayı Değil

Bir sürenin kendisi de Go’da ayrı bir tip taşıyor: time.Duration, altında bir int64 saklayan ama kendi başına bir tip olan, nanosaniye cinsinden bir süre. Bir sayıdan farkı, bir sayının aksine kendi biçimlendirmesini (1h30m0s gibi) taşıması ve başka bir süreyle karıştırılmasının derleyici tarafından reddedilmesi.

SS10. — Bir süre dizgisi ("1h30m") ayrıştırılıp tipi ve biçimlendirilmiş yazımı ölçülüyor; ardından aynı değerin ham bir tam sayı karşılığıyla eşit olup olmadığı ve iki katına katlandığında biçimlendirmenin nasıl değiştiği koşturuluyor.

// sure-tipi.go — time.Duration kendi tipini tasiyor, ham sayidan ayri
package main

import (
	"fmt"
	"time"
)

func main() {
	sure, _ := time.ParseDuration("1h30m")
	fmt.Printf("sure tipi=%T deger=%s\n", sure, sure)
	fmt.Println("int64 karsiligina esit mi:", int64(sure) == 5400_000_000_000)

	ikiKat := sure * 2
	fmt.Println("iki kati:", ikiKat)
	fmt.Println("saat cinsinden:", ikiKat.Hours())
}
sure tipi=time.Duration deger=1h30m0s
int64 karsiligina esit mi: true
iki kati: 3h0m0s
saat cinsinden: 3

sure değişkeninin altında yatan veri bir int64 — nanosaniye sayısı — ama %T onu ham bir tam sayı olarak değil, time.Duration olarak gösteriyor, ve fmt.Println onu doğrudan yazdırdığında 5400000000000 değil 1h30m0s çıkıyor. Bu, tam sayı ile süre arasında derleyicinin kurduğu bir ayrım: sureün altındaki int64 değeri ile başka bir int64 değişken arasında örtük dönüşüm yok, ikisi ayrı tipler. ikiKat := sure * 2 çarpımı süre tipini koruyor ve biçimlendirmesi de otomatik olarak güncelleniyor — 3h0m0s, Hours() yöntemiyle sorgulandığında da 3 veriyor.

Süre de, tıpkı bir an gibi, metne dönüp geri gelebiliyor — ve bu geçiş bir anın geçişinden daha az kayıplı çıkıyor.

SS11.sure değişkeni String() ile metne çevrilip aynı metin ParseDuration ile geri okunuyor; ayrıca sözdizimi bozuk bir metin de deneniyor.

// sure-metne-geri.go — sure metne donup geri geliyor, gecersiz metin reddediliyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sure, _ := time.ParseDuration("1h30m")
	metin := sure.String()
	geri, hataGeri := time.ParseDuration(metin)
	fmt.Println("sure metne donusumu:", metin)
	fmt.Println("geri ayni deger mi :", geri == sure)
	fmt.Println("geri okuma hatasiz mi:", hataGeri == nil)

	_, hataGecersiz := time.ParseDuration("1h30mx")
	fmt.Println("gecersiz metin hata verdi mi:", hataGecersiz != nil)
}
sure metne donusumu: 1h30m0s
geri ayni deger mi : true
geri okuma hatasiz mi: true
gecersiz metin hata verdi mi: true

Bir anın metne dönüşünden farklı olarak, time.Durationın yazımı hiçbir bilgi kaybetmiyor: geri == sure karşılaştırması true, çünkü String()ün ürettiği metin (1h30m0s) süreyi oluşturan her birimi (saat, dakika, saniye) tam olarak taşıyor — bir sürenin bir “adı” ya da “bağlamı” yok, taşıdığı tek bilgi kendi uzunluğu, ve bu bilgi metne tam olarak sığıyor. Bu, saat diliminin adını taşıyamayan bir önceki ölçümle tam bir karşıtlık: kaybın olup olmaması, taşınan değerin kendisinin ne kadar bilgi içerdiğine bağlı, kullanılan yazım biçiminin kendisine değil. Son satır ayrı bir gözlem ekliyor: sözdizimi bozuk bir süre metni sessizce 0a düşmüyor, ParseDuration bunu bir hatayla reddediyor — süre burada da derleme zamanı değil ama en azından ayrıştırma zamanı bir denetimden geçiyor.

Sınırlayıcı Ölçüm: Aynı An, İki Yazım

Buraya kadarki ölçümler bir anı tek bir saat diliminde inceledi. Aşağıdaki adım aynı anı iki farklı kurgusal bölgede temsil edip üç ayrı karşılaştırma aracını (Equal, ==, String) yan yana koşturuyor.

SS12. — İki kurgusal bölge (BolgeA +03:00, BolgeB −05:00) aynı sabit ana uygulanıyor. Üçü de “aynı mı” sorusuna farklı bir yanıt veriyor.

// iki-yazim.go — ayni an iki bolgede, uc ayri karsilastirma araciyla olculuyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	bolgeA := time.FixedZone("BolgeA", 3*3600)
	bolgeB := time.FixedZone("BolgeB", -5*3600)

	gorunumA := sabitAn.In(bolgeA)
	gorunumB := sabitAn.In(bolgeB)

	fmt.Println("BolgeA yerel saat :", gorunumA.Format("15:04"))
	fmt.Println("BolgeB yerel saat :", gorunumB.Format("15:04"))
	fmt.Println("Equal ile ayni mi :", gorunumA.Equal(gorunumB))
	fmt.Println("== ile ayni mi    :", gorunumA == gorunumB)
	fmt.Println("String() ayni mi  :", gorunumA.String() == gorunumB.String())
}
BolgeA yerel saat : 13:00
BolgeB yerel saat : 05:00
Equal ile ayni mi : true
== ile ayni mi    : false
String() ayni mi  : false

Üç karşılaştırma aracı üç farklı soruya yanıt veriyor. Equal, iki değerin temsil ettiği anı karşılaştırıyor — ikisi de aynı ana ait olduğu için sonuç true. == işleci ise time.Timein iç yapısını birebir karşılaştırıyor; bu yapı saat dilimi bilgisini de içerdiğinden, iki farklı bölgeye bağlı iki değer == ile eşit çıkmıyor. String() de aynı nedenle farklı: her biri kendi yerel saatini ve ofsetini yazıyor, biri “13:00 +03:00” biri “05:00 -05:00” — metin düzeyinde hiç örtüşmüyorlar.

Bu üçü arasındaki fark, Go’nun belgesinde açıkça uyarılan bir tuzak: == işleci time.Time değerleri için kullanılmaması gereken bir karşılaştırma, çünkü onu kullanan kod “aynı an mı” sorusunu değil, “aynı temsil mi” sorusunu yanıtlıyor — çoğu zaman istenen ilk soru, ama işlecin kendisi ikinciye yanıt veriyor. Konunun ölçü ekseninde bu, bu derse özgü bir sınır çiziyor: iki değer aynı anı gösterdiğinde bile, karşılaştırma aracının kendisi seçilen soruyu belirliyor; “aynı” sözcüğü tek başına hangi karşılaştırmanın kastedildiğini söylemiyor.

Aynı ayrım sıralama için de geçerli. Before ve After yöntemleri, tıpkı Equal gibi, temsile değil ana bakıyor; iki değer farklı bölgelerde yazılmış olsa bile hangisinin zamanda daha önce geldiğini doğru söylüyor.

// siralama.go — Before ve After da temsile degil ana bakiyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	birSaatSonra := sabitAn.Add(time.Hour)

	bolgeA := time.FixedZone("BolgeA", 3*3600)
	gorunumA := sabitAn.In(bolgeA)
	gorunumSonraB := birSaatSonra.In(time.FixedZone("BolgeB", -5*3600))

	fmt.Println("gorunumA, gorunumSonraB'den once mi:", gorunumA.Before(gorunumSonraB))
	fmt.Println("gorunumSonraB, gorunumA'dan sonra mi:", gorunumSonraB.After(gorunumA))
}
gorunumA, gorunumSonraB'den once mi: true
gorunumSonraB, gorunumA'dan sonra mi: true

İki değer birbirinden tamamen farklı bölgelerde ve tamamen farklı yerel saatlerde yazılı olmasına rağmen Before ve After doğru sırayı buluyor, çünkü ikisi de karşılaştırmayı temsil edilen an üzerinden yapıyor — == işlecinin yaptığı gibi iç yapının birebir eşleşmesini aramıyor. Bu, önceki paragrafın genel kuralını doğruluyor: time.Time değerlerini karşılaştırırken Equal, Before ve After güvenli bir taban oluşturuyor, == ise yalnızca iki değerin aynı bölgede, aynı biçimde üretildiği bilindiğinde güvenli.

Özet

  • Bir an metne dönüp aynı düzenle geri okunduğunda Equal ile korunuyor; bu, konunun ortak çekirdeğindeki “yeniden kuruluyor” satırının doğrulanmış hâli.
  • RFC 3339 düzeni bir saat diliminin sayısal ofsetini taşıyor ama adını taşımıyor: geri kurulan değerin ofseti orijinaliyle eşleşirken bölge adı eşleşmiyor.
  • time.Duration altında bir int64 taşısa da kendi tipini koruyor; biçimlendirmesi ve aritmetiği bu tipe göre işliyor, ham bir sayıyla karıştırılmıyor. Metne dönüp geri gelmesi de bilgi kaybetmiyor, çünkü taşıdığı tek şey kendi uzunluğu.
  • Sınırlayıcı ölçüm: aynı anı gösteren iki değer Equal ile eşit çıkarken == ve String() ile farklı çıkıyor — üç araç üç farklı soruya yanıt veriyor.
  • Before ve After da Equal gibi ana bakıyor, temsile değil; == işleci ise yalnızca iki değerin aynı bölgede ve biçimde üretildiği bilindiğinde güvenli bir karşılaştırma.

Sonraki Adım

Bu derste bir zaman değerinin metne dönüşürken alan adı taşımadığı, yalnızca ham bir yazım ürettiği görüldü — 2026-08-23T13:00:00+03:00 metninin hangi alanın hangi değeri olduğunu söyleyen hiçbir işaret yok, yalnızca konumdan çıkarılabiliyor. Bir okuyucu bu metni doğru yorumlayabiliyor çünkü RFC 3339 düzenini zaten biliyor — tıpkı bir önceki dersteki gömme yönergesinin, hedef tipi zaten bilen bir değişkene içerik yerleştirmesi gibi. Ama her biçim bu kadar cömert değil: konumdan çıkarılan bir alan, konum kaybolduğunda ya da biçim değiştiğinde bir daha çıkarılamıyor.

Sıradaki ders bu boşluğu tam olarak dolduran bir biçimi ölçüyor: bir olayı günlüğe yazarken alan adlarını da taşımak, aynı kaydı hem insan hem başka bir program için ayrıştırılabilir kılıyor mu? Bu dersin sabit zaman damgası, sıradaki dersin günlük kayıtlarında da aynen kullanılacak — zaman burada da gerçek saatten değil, sabit bir metinden gelecek.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat