Time #
Waktu adalah salah satu konsep yang tampak sederhana tapi penuh jebakan di dunia pemrograman — zona waktu yang berbeda, format tanggal yang tidak konsisten, daylight saving time, presisi nanosecond, perbandingan waktu yang salah, dan scheduling yang tidak akurat. Package time di Go menangani semua kerumitan ini dengan API yang bersih dan konsisten. Ia menyediakan tipe time.Time untuk merepresentasikan momen tertentu, time.Duration untuk interval waktu, fungsi parsing dan formatting dengan pola yang unik, serta Timer dan Ticker untuk scheduling. Memahami package time dengan baik bukan hanya tentang cara mencetak tanggal — ini tentang menghindari bug zona waktu yang halus, mengelola timeout yang benar, dan membangun sistem yang berperilaku deterministik terhadap waktu.
Gambaran Besar Package time #
flowchart TD
T["package time"] --> Repr["Representasi Waktu"]
T --> Parse["Parsing & Formatting"]
T --> Arith["Aritmetika Waktu"]
T --> Sched["Scheduling"]
T --> Zone["Zona Waktu"]
Repr --> R1["time.Time — momen spesifik"]
Repr --> R2["time.Duration — interval waktu"]
Repr --> R3["time.Now() — waktu saat ini"]
Parse --> P1["time.Parse — string → Time"]
Parse --> P2["t.Format — Time → string"]
Parse --> P3["time.RFC3339, time.Kitchen, dll"]
Arith --> A1["t.Add(d) — tambah durasi"]
Arith --> A2["t.Sub(t2) — selisih dua waktu"]
Arith --> A3["t.Before / t.After / t.Equal"]
Sched --> S1["time.Sleep — tunda eksekusi"]
Sched --> S2["time.After — channel setelah durasi"]
Sched --> S3["time.NewTimer — one-shot timer"]
Sched --> S4["time.NewTicker — ticker periodik"]
Zone --> Z1["time.LoadLocation — muat zona waktu"]
Zone --> Z2["t.In(loc) — konversi zona"]
Zone --> Z3["time.UTC / time.Local"]
style T fill:#4f86c6,color:#fff
style Repr fill:#e8f5e9
style Parse fill:#e3f2fd
style Arith fill:#fff3e0
style Sched fill:#fce4ec
style Zone fill:#f3e5f5time.Time — Merepresentasikan Waktu #
time.Time adalah tipe utama package time. Ia merepresentasikan sebuah momen dalam waktu dengan presisi nanosecond, dan selalu menyimpan informasi zona waktu (timezone) bersama nilainya.
package main
import (
"fmt"
"time"
)
func main() {
// Waktu saat ini — dengan zona waktu lokal
sekarang := time.Now()
fmt.Println(sekarang)
// 2024-03-15 14:30:00.123456789 +0700 WIB
// Membuat time.Time dari komponen tertentu
ulangTahun := time.Date(1990, time.March, 15, 0, 0, 0, 0, time.Local)
fmt.Println(ulangTahun)
// 1990-03-15 00:00:00 +0700 WIB
// Waktu dalam UTC
sekarangUTC := time.Now().UTC()
fmt.Println(sekarangUTC)
// 2024-03-15 07:30:00.123456789 +0000 UTC
// Zero value time.Time — January 1, year 1, 00:00:00 UTC
var t time.Time
fmt.Println(t) // 0001-01-01 00:00:00 +0000 UTC
fmt.Println(t.IsZero()) // true — berguna untuk memeriksa "belum diset"
// Unix timestamp — detik sejak 1 Januari 1970 UTC
sekarang = time.Now()
fmt.Println(sekarang.Unix()) // 1710491400
fmt.Println(sekarang.UnixMilli()) // 1710491400123 — milidetik
fmt.Println(sekarang.UnixNano()) // 1710491400123456789 — nanosecond
// Dari Unix timestamp
dariUnix := time.Unix(1710491400, 0)
fmt.Println(dariUnix)
// 2024-03-15 14:30:00 +0700 WIB
}
Mengakses Komponen Waktu #
t := time.Now()
// Komponen tanggal
fmt.Println(t.Year()) // 2024
fmt.Println(t.Month()) // March (tipe time.Month)
fmt.Println(int(t.Month())) // 3
fmt.Println(t.Day()) // 15
fmt.Println(t.Weekday()) // Friday (tipe time.Weekday)
fmt.Println(int(t.Weekday())) // 5 (0=Sunday, 6=Saturday)
// Komponen waktu
fmt.Println(t.Hour()) // 14
fmt.Println(t.Minute()) // 30
fmt.Println(t.Second()) // 0
fmt.Println(t.Nanosecond()) // 123456789
// Hari ke-berapa dalam tahun (1-365/366)
fmt.Println(t.YearDay()) // 75
// Week number ISO 8601
tahun, minggu := t.ISOWeek()
fmt.Printf("Tahun %d, Minggu ke-%d\n", tahun, minggu)
// Date dan Clock dalam satu panggilan
tahun2, bulan, hari := t.Date()
jam, menit, detik := t.Clock()
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n",
tahun2, bulan, hari, jam, menit, detik)
Parsing dan Formatting #
Go menggunakan pendekatan unik untuk format waktu: alih-alih simbol seperti YYYY-MM-DD, Go menggunakan waktu referensi spesifik — Mon Jan 2 15:04:05 MST 2006. Setiap komponen format diwakili oleh nilai dari waktu referensi ini.
flowchart LR
subgraph Ref["Waktu Referensi Go"]
direction TB
R1["2006 → tahun\n(tahun ke-6 dalam urutan 1 2 3 4 5 6)"]
R2["01 → bulan\n(01=Januari)"]
R3["02 → hari\n(02=tanggal 2)"]
R4["15 → jam 24h\n(15:00 = 3pm)"]
R5["04 → menit\n(04)"]
R6["05 → detik\n(05)"]
R7["MST → zona waktu\n(Mountain Standard Time)"]
end
subgraph Format["Contoh Format"]
direction TB
F1["2006-01-02 → YYYY-MM-DD"]
F2["02/01/2006 → DD/MM/YYYY"]
F3["15:04:05 → HH:MM:SS"]
F4["2006-01-02T15:04:05Z07:00 → RFC3339"]
F5["Jan 2, 2006 → 'Mar 15, 2024'"]
end
Ref --> Formatt := time.Now()
// Format ke string
fmt.Println(t.Format("2006-01-02"))
// 2024-03-15
fmt.Println(t.Format("02/01/2006"))
// 15/03/2024
fmt.Println(t.Format("2006-01-02 15:04:05"))
// 2024-03-15 14:30:00
fmt.Println(t.Format("Monday, 02 January 2006"))
// Friday, 15 March 2024
fmt.Println(t.Format("15:04:05.000"))
// 14:30:00.123 — milidetik
// Konstanta format bawaan
fmt.Println(t.Format(time.RFC3339))
// 2024-03-15T14:30:00+07:00
fmt.Println(t.Format(time.RFC3339Nano))
// 2024-03-15T14:30:00.123456789+07:00
fmt.Println(t.Format(time.RFC1123))
// Fri, 15 Mar 2024 14:30:00 WIB
fmt.Println(t.Format(time.Kitchen))
// 2:30PM
fmt.Println(t.Format(time.DateOnly)) // Go 1.20+
// 2024-03-15
fmt.Println(t.Format(time.TimeOnly)) // Go 1.20+
// 14:30:00
Parsing String ke time.Time #
// time.Parse — parse dengan zona waktu dari format string
t1, err := time.Parse("2006-01-02", "2024-03-15")
if err != nil {
fmt.Println("parse error:", err)
return
}
fmt.Println(t1) // 2024-03-15 00:00:00 +0000 UTC
// PERHATIAN: zona waktu adalah UTC jika tidak ada di format!
// time.ParseInLocation — parse dengan zona waktu eksplisit
loc, _ := time.LoadLocation("Asia/Jakarta")
t2, err := time.ParseInLocation("2006-01-02 15:04:05",
"2024-03-15 14:30:00", loc)
if err != nil {
fmt.Println("parse error:", err)
return
}
fmt.Println(t2) // 2024-03-15 14:30:00 +0700 WIB
// Parse RFC3339 — format yang paling dianjurkan untuk API
t3, err := time.Parse(time.RFC3339, "2024-03-15T14:30:00+07:00")
fmt.Println(t3) // 2024-03-15 14:30:00 +0700 +0700
// ANTI-PATTERN: asumsikan format tanpa validasi
func parseHari(s string) time.Time {
t, _ := time.Parse("2006-01-02", s) // abaikan error — berbahaya!
return t // mengembalikan zero value jika parse gagal, tanpa peringatan
}
// BENAR: selalu periksa error parsing
func parseHariBaik(s string) (time.Time, error) {
t, err := time.Parse("2006-01-02", s)
if err != nil {
return time.Time{}, fmt.Errorf("parseHari %q: %w", s, err)
}
return t, nil
}
Format untuk Indonesia #
// Nama bulan dan hari dalam Bahasa Indonesia
var namaBulan = []string{
"", "Januari", "Februari", "Maret", "April", "Mei", "Juni",
"Juli", "Agustus", "September", "Oktober", "November", "Desember",
}
var namaHari = []string{
"Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu",
}
func formatIndonesia(t time.Time) string {
return fmt.Sprintf("%s, %d %s %d",
namaHari[t.Weekday()],
t.Day(),
namaBulan[t.Month()],
t.Year(),
)
}
func formatWaktuIndonesia(t time.Time) string {
return fmt.Sprintf("%s pukul %02d.%02d WIB",
formatIndonesia(t),
t.Hour(),
t.Minute(),
)
}
t := time.Now()
fmt.Println(formatIndonesia(t))
// Jumat, 15 Maret 2024
fmt.Println(formatWaktuIndonesia(t))
// Jumat, 15 Maret 2024 pukul 14.30 WIB
time.Duration — Merepresentasikan Interval #
time.Duration adalah int64 yang merepresentasikan interval waktu dalam nanosecond. Go menyediakan konstanta untuk satuan yang umum digunakan.
// Konstanta durasi
fmt.Println(time.Nanosecond) // 1ns
fmt.Println(time.Microsecond) // 1µs
fmt.Println(time.Millisecond) // 1ms
fmt.Println(time.Second) // 1s
fmt.Println(time.Minute) // 1m0s
fmt.Println(time.Hour) // 1h0m0s
// Membuat durasi
setengan := 30 * time.Minute // 30m0s
satuJamSetengah := 90 * time.Minute // 1h30m0s
tigaHari := 3 * 24 * time.Hour // 72h0m0s
// Konversi durasi ke satuan numerik
d := 2*time.Hour + 30*time.Minute + 15*time.Second
fmt.Println(d) // 2h30m15s
fmt.Println(d.Hours()) // 2.504166... (float64)
fmt.Println(d.Minutes()) // 150.25 (float64)
fmt.Println(d.Seconds()) // 9015 (float64)
fmt.Println(d.Milliseconds()) // 9015000 (int64)
fmt.Println(d.Nanoseconds()) // 9015000000000 (int64)
// Parsing durasi dari string
d2, err := time.ParseDuration("2h30m15s")
d3, err := time.ParseDuration("1.5h")
d4, err := time.ParseDuration("300ms")
d5, err := time.ParseDuration("2.5s")
// Pembulatan dan truncate
d6 := 2*time.Hour + 37*time.Minute + 42*time.Second
fmt.Println(d6.Round(time.Minute)) // 2h38m0s — dibulatkan ke menit
fmt.Println(d6.Truncate(time.Minute)) // 2h37m0s — dipotong ke menit
fmt.Println(d6.Abs()) // 2h37m42s — nilai absolut (Go 1.19+)
Aritmetika Waktu #
Operasi aritmetika pada waktu — menambah, mengurangi, dan membandingkan — adalah hal yang sangat sering dilakukan dan mudah salah jika tidak hati-hati.
flowchart LR
T1["time.Time\nt1"]
T2["time.Time\nt2"]
D["time.Duration\nd"]
T1 -- "t1.Add(d)" --> T3["time.Time\nt1 + d"]
T1 -- "t1.Sub(t2)" --> D2["time.Duration\nt1 - t2"]
T2 -- "t2.Add(-d)" --> T4["time.Time\nt2 - d"]
T1 -- "t1.Before(t2)" --> B["bool"]
T1 -- "t1.After(t2)" --> B
T1 -- "t1.Equal(t2)" --> B
style T1 fill:#e3f2fd
style T2 fill:#e3f2fd
style D fill:#fff3e0
style D2 fill:#fff3e0
style T3 fill:#e8f5e9
style T4 fill:#e8f5e9sekarang := time.Now()
// Add — tambahkan durasi ke waktu
besok := sekarang.Add(24 * time.Hour)
sejamLagi := sekarang.Add(time.Hour)
limaMenitLalu := sekarang.Add(-5 * time.Minute)
// AddDate — tambahkan tahun, bulan, hari (lebih natural untuk kalender)
bulanDepan := sekarang.AddDate(0, 1, 0) // +1 bulan
tahunDepan := sekarang.AddDate(1, 0, 0) // +1 tahun
semingguLalu := sekarang.AddDate(0, 0, -7) // -7 hari
// PERHATIAN: AddDate menangani overflow bulan secara otomatis
// 31 Januari + 1 bulan = 3 Maret (bukan 31 Februari)
akhirJan := time.Date(2024, time.January, 31, 0, 0, 0, 0, time.UTC)
fmt.Println(akhirJan.AddDate(0, 1, 0))
// 2024-03-02 00:00:00 +0000 UTC (bukan 2024-02-31!)
// Sub — selisih antara dua waktu, menghasilkan Duration
mulai := time.Now()
time.Sleep(100 * time.Millisecond)
selesai := time.Now()
elapsed := selesai.Sub(mulai)
fmt.Printf("Waktu eksekusi: %v\n", elapsed) // 100.123ms
// Since dan Until — shortcut yang sangat umum dipakai
fmt.Println(time.Since(mulai)) // setara dengan time.Now().Sub(mulai)
fmt.Println(time.Until(besok)) // setara dengan besok.Sub(time.Now())
// Perbandingan waktu
t1 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
t2 := time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(t1.Before(t2)) // true
fmt.Println(t1.After(t2)) // false
fmt.Println(t1.Equal(t2)) // false
// ANTI-PATTERN: bandingkan dengan == — tidak memperhitungkan zona waktu
tJakarta := time.Date(2024, 1, 1, 7, 0, 0, 0, loc) // 07:00 WIB
tUTC := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) // 00:00 UTC
fmt.Println(tJakarta == tUTC) // false! padahal momen yang sama
fmt.Println(tJakarta.Equal(tUTC)) // true — ini yang benar
Zona Waktu #
Zona waktu adalah sumber bug yang paling sering tidak terdeteksi pada sistem yang berhubungan dengan waktu. Prinsip dasarnya sederhana: selalu simpan dan transfer waktu dalam UTC, konversi ke zona lokal hanya saat menampilkan ke pengguna.
flowchart TD
subgraph Prinsip["Prinsip Zona Waktu"]
direction LR
P1["Simpan di DB\ndalam UTC"]
P2["Transfer via API\ndalam RFC3339 + offset"]
P3["Tampilkan ke user\ndalam zona lokal"]
P1 --> P2 --> P3
end
subgraph Go["Di Go"]
direction TB
G1["time.Now().UTC()\nuntuk operasi internal"]
G2["t.In(loc)\nuntuk konversi tampilan"]
G3["time.LoadLocation('Asia/Jakarta')\nuntuk zona Indonesia"]
end
Prinsip --> Go// Muat zona waktu dari database IANA
// Membutuhkan tzdata — tersedia di semua OS modern
loc, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
fmt.Fprintf(os.Stderr, "gagal muat timezone: %v\n", err)
return
}
// Zona waktu lain yang umum di Indonesia
locWITA, _ := time.LoadLocation("Asia/Makassar") // WIB+1
locWIT, _ := time.LoadLocation("Asia/Jayapura") // WIB+2
// Zona tetap (fixed offset) — tidak terpengaruh DST
wib := time.FixedZone("WIB", 7*60*60) // UTC+7
wita := time.FixedZone("WITA", 8*60*60) // UTC+8
wit := time.FixedZone("WIT", 9*60*60) // UTC+9
// Konversi antar zona
sekarang := time.Now().UTC()
fmt.Println("UTC :", sekarang.Format("15:04:05 MST"))
fmt.Println("WIB :", sekarang.In(loc).Format("15:04:05 MST"))
fmt.Println("Tokyo:", sekarang.In(mustLoadLoc("Asia/Tokyo")).Format("15:04:05 MST"))
fmt.Println("NYC :", sekarang.In(mustLoadLoc("America/New_York")).Format("15:04:05 MST"))
func mustLoadLoc(name string) *time.Location {
loc, err := time.LoadLocation(name)
if err != nil {
panic(err)
}
return loc
}
// Membuat time.Time di zona tertentu
deadline := time.Date(2024, 3, 15, 17, 0, 0, 0, loc) // 17:00 WIB
fmt.Println(deadline.UTC()) // 2024-03-15 10:00:00 +0000 UTC
time.Localmenggunakan zona waktu sistem operasi yang menjalankan program — ini bisa berbeda antara mesin developer dan server produksi. Di container yang berbasis Linux,time.Localbiasanya UTC kecuali dikonfigurasi secara eksplisit. Selalu gunakantime.LoadLocationdengan nama zona yang eksplisit, atautime.UTCuntuk operasi internal.
Menangani DST (Daylight Saving Time) #
Indonesia tidak menerapkan DST, tapi sistem yang berinteraksi dengan zona waktu lain perlu berhati-hati:
// DST menyebabkan "jam yang hilang" dan "jam yang ganda"
nyLoc, _ := time.LoadLocation("America/New_York")
// "Jam yang hilang" — 2:30 AM tidak ada saat spring forward
// Go menangani ini secara otomatis dengan menyesuaikan ke jam yang valid
jamHilang := time.Date(2024, 3, 10, 2, 30, 0, 0, nyLoc)
fmt.Println(jamHilang) // otomatis disesuaikan
// Hitung selisih waktu melintasi DST — gunakan Sub, bukan AddDate
sebelumDST := time.Date(2024, 3, 9, 12, 0, 0, 0, nyLoc)
setelahDST := time.Date(2024, 3, 11, 12, 0, 0, 0, nyLoc)
// Selisih adalah 47 jam, bukan 48 jam!
fmt.Println(setelahDST.Sub(sebelumDST)) // 47h0m0s
Timer dan Ticker — Scheduling #
time.Timer dan time.Ticker adalah mekanisme untuk mengeksekusi sesuatu di masa depan atau secara periodik. Keduanya menggunakan channel sebagai sinyal.
flowchart TD
subgraph Timer["time.Timer — One-shot"]
T1["time.NewTimer(d)"] --> T2["Tunggu..."]
T2 --> T3["<-timer.C\nterima sekali setelah d"]
T1 --> T4["timer.Stop()\nbatalkan sebelum fire"]
T1 --> T5["timer.Reset(d)\natur ulang durasi"]
end
subgraph Ticker["time.Ticker — Periodik"]
K1["time.NewTicker(d)"] --> K2["Tick..."]
K2 --> K3["<-ticker.C\nterima setiap d"]
K3 --> K2
K1 --> K4["ticker.Stop()\nhentikan ticker"]
end
subgraph After["Shortcut"]
A1["time.After(d)\nreturn <-chan Time"]
A2["time.Sleep(d)\nblokir goroutine"]
A3["time.AfterFunc(d, f)\njalankan f setelah d"]
end
style Timer fill:#e8f5e9
style Ticker fill:#e3f2fd
style After fill:#fff3e0time.Sleep — Menunda Eksekusi #
// Tunda eksekusi goroutine saat ini
fmt.Println("Mulai...")
time.Sleep(2 * time.Second)
fmt.Println("2 detik berlalu")
// Sleep dengan durasi dari string
durasi, _ := time.ParseDuration("500ms")
time.Sleep(durasi)
time.Timer — One-shot Timer #
// Timer yang fire sekali setelah durasi tertentu
timer := time.NewTimer(3 * time.Second)
fmt.Println("Menunggu timer...")
<-timer.C // blokir sampai timer fire
fmt.Println("Timer fired!")
// Membatalkan timer sebelum fire
timer2 := time.NewTimer(10 * time.Second)
go func() {
time.Sleep(2 * time.Second)
// Hentikan timer — kembalikan true jika berhasil dihentikan
if timer2.Stop() {
fmt.Println("Timer dibatalkan")
}
}()
select {
case <-timer2.C:
fmt.Println("Timer fired")
case <-time.After(5 * time.Second):
fmt.Println("Timeout menunggu timer")
}
// ANTI-PATTERN: Reset tanpa drain channel terlebih dahulu
timer3 := time.NewTimer(time.Second)
timer3.Stop()
timer3.Reset(2 * time.Second) // mungkin ada nilai di channel!
// BENAR: Stop lalu drain sebelum Reset
timer4 := time.NewTimer(time.Second)
if !timer4.Stop() {
<-timer4.C // drain channel jika sudah fire
}
timer4.Reset(2 * time.Second)
time.Ticker — Eksekusi Periodik #
// Ticker yang berdetak setiap interval
ticker := time.NewTicker(time.Second)
defer ticker.Stop() // WAJIB: stop ticker saat selesai, hindari goroutine leak
batas := time.After(5 * time.Second)
for {
select {
case t := <-ticker.C:
fmt.Println("Tick:", t.Format("15:04:05"))
case <-batas:
fmt.Println("Selesai")
return
}
}
// Output:
// Tick: 14:30:01
// Tick: 14:30:02
// Tick: 14:30:03
// Tick: 14:30:04
// Selesai
Selalu panggilticker.Stop()setelah selesai menggunakan Ticker — biasanya dengandefer ticker.Stop(). Ticker yang tidak dihentikan akan terus berjalan dan goroutine yang membaca dariticker.Ctidak akan pernah selesai (goroutine leak). Ini adalah salah satu sumber memory leak yang paling umum di program Go yang menggunakan Ticker.
time.AfterFunc — Eksekusi Asinkron #
// Jalankan fungsi di goroutine baru setelah durasi tertentu
timer := time.AfterFunc(5*time.Second, func() {
fmt.Println("Ini dijalankan di goroutine terpisah setelah 5 detik!")
// Hati-hati dengan akses ke shared state — perlu sinkronisasi
})
// Batalkan jika perlu
timer.Stop()
// Pola: retry dengan backoff eksponensial
func retryDenganBackoff(fn func() error, maxRetry int) error {
var err error
for i := 0; i < maxRetry; i++ {
if err = fn(); err == nil {
return nil
}
if i < maxRetry-1 {
backoff := time.Duration(1<<uint(i)) * 100 * time.Millisecond
// cap backoff maksimal 30 detik
if backoff > 30*time.Second {
backoff = 30 * time.Second
}
fmt.Printf("Retry %d/%d setelah %v: %v\n", i+1, maxRetry, backoff, err)
time.Sleep(backoff)
}
}
return fmt.Errorf("gagal setelah %d retry: %w", maxRetry, err)
}
Timeout dengan time.After dan context #
Timeout adalah pola yang sangat umum di aplikasi Go — membatasi berapa lama sebuah operasi boleh berjalan. Ada dua cara utama: time.After untuk kasus sederhana, dan context.WithTimeout untuk integrasi yang lebih baik dengan stack Go.
sequenceDiagram
participant Main as Goroutine Utama
participant Op as Operasi (DB, HTTP, dll)
participant Timer as time.After / context
Main->>Op: Mulai operasi
Main->>Timer: Set timeout (misalnya 5 detik)
alt Operasi selesai tepat waktu
Op-->>Main: Hasil
Main->>Timer: Stop timer (jika Timer)
else Timeout lebih dulu
Timer-->>Main: Sinyal timeout
Main->>Op: Cancel / abaikan hasil terlambat
Main-->>Main: Return error timeout
endimport (
"context"
"fmt"
"time"
)
// Pola 1: time.After — untuk goroutine sederhana
func operasiDenganTimeout(durasi time.Duration) error {
hasil := make(chan string, 1)
go func() {
// Simulasi operasi yang memakan waktu
time.Sleep(3 * time.Second)
hasil <- "selesai"
}()
select {
case r := <-hasil:
fmt.Println("Berhasil:", r)
return nil
case <-time.After(durasi):
return fmt.Errorf("operasi timeout setelah %v", durasi)
}
}
// Pola 2: context.WithTimeout — cara yang dianjurkan untuk produksi
func queryDenganTimeout(ctx context.Context, id int) (string, error) {
// Buat context baru dengan timeout 5 detik
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() // WAJIB: selalu cancel untuk bebaskan resource
// Semua operasi yang mendukung context (DB, HTTP client, dll)
// akan otomatis dibatalkan saat context timeout
hasil := make(chan string, 1)
go func() {
// Simulasi DB query
time.Sleep(2 * time.Second)
hasil <- fmt.Sprintf("data untuk id %d", id)
}()
select {
case r := <-hasil:
return r, nil
case <-ctx.Done():
return "", fmt.Errorf("queryDenganTimeout: %w", ctx.Err())
}
}
// Contoh penggunaan
func main() {
ctx := context.Background()
data, err := queryDenganTimeout(ctx, 42)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return
}
fmt.Println(data)
}
Mengukur Waktu Eksekusi #
Mengukur berapa lama sebuah fungsi atau blok kode berjalan adalah kebutuhan umum untuk profiling dan optimasi.
// Pola 1: manual dengan time.Now dan time.Since
func ukurWaktu(nama string, fn func()) {
mulai := time.Now()
fn()
fmt.Printf("%s selesai dalam %v\n", nama, time.Since(mulai))
}
// Pola 2: defer untuk automatic timing
func prosesData(data []int) {
defer func(mulai time.Time) {
fmt.Printf("prosesData(%d items) took %v\n",
len(data), time.Since(mulai))
}(time.Now()) // time.Now() dievaluasi saat defer dideklarasikan!
// ... logika pemrosesan
time.Sleep(100 * time.Millisecond)
}
// Pola 3: untuk benchmark dalam test
func BenchmarkProses(b *testing.B) {
for i := 0; i < b.N; i++ {
// Fungsi yang diukur
prosesSesuatu()
}
}
// Contoh penggunaan manual timer
mulai := time.Now()
for i := 0; i < 1000000; i++ {
// operasi
}
elapsed := time.Since(mulai)
fmt.Printf("1 juta iterasi: %v (%.2f ns/op)\n",
elapsed,
float64(elapsed.Nanoseconds())/1000000)
Pola Penggunaan di Produksi #
Scheduler Sederhana #
// Jalankan tugas setiap hari pukul 02:00
func jadwalkanHarian(jam, menit int, tugas func()) {
for {
sekarang := time.Now()
// Hitung waktu berikutnya
berikutnya := time.Date(
sekarang.Year(), sekarang.Month(), sekarang.Day(),
jam, menit, 0, 0, sekarang.Location(),
)
// Jika jam target sudah lewat hari ini, jadwalkan untuk besok
if berikutnya.Before(sekarang) {
berikutnya = berikutnya.Add(24 * time.Hour)
}
selisih := time.Until(berikutnya)
fmt.Printf("Tugas berikutnya dalam %v (pukul %02d:%02d)\n",
selisih.Round(time.Minute), jam, menit)
<-time.After(selisih)
go tugas() // jalankan di goroutine agar tidak blokir scheduler
}
}
Rate Limiter Sederhana dengan Ticker #
// Batasi eksekusi ke N operasi per detik
func buatRateLimiter(opsPerDetik int) <-chan time.Time {
return time.NewTicker(time.Second / time.Duration(opsPerDetik)).C
}
func main() {
// Maksimal 5 request per detik
rateLimiter := buatRateLimiter(5)
permintaan := []string{"req1", "req2", "req3", "req4", "req5", "req6", "req7"}
for _, req := range permintaan {
<-rateLimiter // tunggu giliran
go prosesPermintaan(req)
}
}
Cache dengan Expiry #
import (
"sync"
"time"
)
type CacheItem struct {
Nilai any
Kadaluarsa time.Time
}
type Cache struct {
mu sync.RWMutex
data map[string]CacheItem
}
func NewCache() *Cache {
c := &Cache{data: make(map[string]CacheItem)}
// Bersihkan item kadaluarsa setiap menit
go c.bersihkanPeriodik()
return c
}
func (c *Cache) Set(key string, nilai any, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = CacheItem{
Nilai: nilai,
Kadaluarsa: time.Now().Add(ttl),
}
}
func (c *Cache) Get(key string) (any, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, ada := c.data[key]
if !ada {
return nil, false
}
// Periksa apakah sudah kadaluarsa
if time.Now().After(item.Kadaluarsa) {
return nil, false
}
return item.Nilai, true
}
func (c *Cache) bersihkanPeriodik() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for range ticker.C {
c.mu.Lock()
sekarang := time.Now()
for key, item := range c.data {
if sekarang.After(item.Kadaluarsa) {
delete(c.data, key)
}
}
c.mu.Unlock()
}
}
Mengukur Umur Sesuatu #
// Hitung umur dalam bahasa manusia
func umurManusiawi(t time.Time) string {
selisih := time.Since(t)
switch {
case selisih < time.Minute:
return "baru saja"
case selisih < time.Hour:
menit := int(selisih.Minutes())
return fmt.Sprintf("%d menit yang lalu", menit)
case selisih < 24*time.Hour:
jam := int(selisih.Hours())
return fmt.Sprintf("%d jam yang lalu", jam)
case selisih < 7*24*time.Hour:
hari := int(selisih.Hours() / 24)
return fmt.Sprintf("%d hari yang lalu", hari)
case selisih < 30*24*time.Hour:
minggu := int(selisih.Hours() / 24 / 7)
return fmt.Sprintf("%d minggu yang lalu", minggu)
case selisih < 365*24*time.Hour:
bulan := int(selisih.Hours() / 24 / 30)
return fmt.Sprintf("%d bulan yang lalu", bulan)
default:
tahun := int(selisih.Hours() / 24 / 365)
return fmt.Sprintf("%d tahun yang lalu", tahun)
}
}
// Contoh penggunaan
postTime := time.Now().Add(-3 * time.Hour)
fmt.Println(umurManusiawi(postTime)) // 3 jam yang lalu
postTime2 := time.Now().Add(-2 * 24 * time.Hour)
fmt.Println(umurManusiawi(postTime2)) // 2 hari yang lalu
Kapan Beralih ke Alternatif #
Tetap gunakan time jika:
✓ Semua operasi dasar tanggal, waktu, dan durasi
✓ Parsing dan formatting waktu dalam berbagai format
✓ Scheduling periodik dengan Ticker dan one-shot dengan Timer
✓ Timeout dan deadline dengan time.After atau context.WithTimeout
✓ Konversi antar zona waktu dengan LoadLocation
Pertimbangkan library eksternal jika:
✗ Kalkulasi kalender yang kompleks (bulan Hijriyah, Jawa, dll)
→ tidak ada library standard untuk ini
✗ Parsing tanggal dari berbagai format yang tidak diketahui
→ dateparse (github.com/araddon/dateparse)
✗ Human-readable duration parsing ("in 2 hours", "yesterday")
→ naturaldate atau library sejenis
✗ Cron expression scheduling ("0 2 * * *")
→ robfig/cron
✗ Manipulasi kalender bisnis (hari kerja, hari libur)
→ tidak ada di stdlib, perlu logika kustom atau library
Ringkasan #
time.Timeselalu menyimpan zona waktu — duatime.Timeyang berbeda zona tapi momen yang sama akanEqualtapi tidak==. Selalu gunakan.Equal()untuk perbandingan, bukan==.- Format Go menggunakan waktu referensi
Mon Jan 2 15:04:05 MST 2006— bukanYYYY-MM-DD. Hafalkan: 2006 (tahun), 01 (bulan), 02 (hari), 15 (jam), 04 (menit), 05 (detik).time.RFC3339adalah format terbaik untuk API — gunakan ini saat serialisasi waktu ke JSON atau HTTP response agar mudah di-parse oleh sistem manapun.- Selalu periksa error dari
time.Parse— parse yang gagal mengembalikan zero value tanpa panik, menyebabkan bug tersembunyi yang sulit dilacak.time.ParseInLocationbukantime.Parsesaat input tidak menyertakan zona waktu —time.Parsemengasumsikan UTC, yang sering bukan yang diinginkan.defer ticker.Stop()wajib setelahtime.NewTicker— Ticker yang tidak dihentikan adalah goroutine leak yang lambat tapi pasti menguras memori.time.Since(t)adalah shortcut untuktime.Now().Sub(t)— gunakan ini untuk mengukur elapsed time dengan ringkas.- Simpan waktu dalam UTC di database dan transfer via API — konversi ke zona lokal hanya saat menampilkan ke pengguna.
context.WithTimeoutlebih baik daritime.Afteruntuk timeout di produksi karena timeout di-propagate ke semua operasi downstream yang mendukung context.AddDatemenangani overflow bulan secara otomatis — 31 Januari + 1 bulan menghasilkan 3 Maret, bukan error atau tanggal tidak valid.