BAB 35: Time, Parsing, dan Formatting
Pelajari cara bekerja dengan tanggal dan waktu di Go menggunakan package time: membuat, mem-parsing, memformat, dan membandingkan nilai time.Time.
Di bab sebelumnya, seed berbasis time.Now().UnixNano() digunakan untuk menghasilkan angka acak yang berbeda setiap eksekusi. Itu adalah penggunaan time yang paling sederhana — hanya mengambil nilai numerik dari waktu saat ini. Tapi time di Go jauh lebih kaya dari itu. Program nyata sering perlu menyimpan, membandingkan, memformat, dan mem-parsing tanggal: kapan sebuah transaksi dicatat, berapa hari lagi tenggat waktu, atau bagaimana mengubah string "2024-03-15" dari input pengguna menjadi nilai yang bisa diolah. Semua itu ditangani oleh package time.
Membuat Nilai Waktu
Ada dua cara utama untuk mendapatkan nilai time.Time di Go: mengambil waktu saat ini, atau membangunnya dari komponen tertentu.
// waktu.go
package main
import (
"fmt"
"time"
)
func main() {
// waktu saat ini
sekarang := time.Now()
fmt.Println("sekarang:", sekarang)
// membangun waktu dari komponen
tenggat := time.Date(2024, time.December, 31, 23, 59, 0, 0, time.UTC)
fmt.Println("tenggat :", tenggat)
}
sekarang: 2024-03-15 09:42:11.382741 +0700 WIB m=+0.000123456
tenggat : 2024-12-31 23:59:00 +0000 UTC
time.Date menerima parameter berurutan: tahun, bulan (sebagai konstanta time.Month), hari, jam, menit, detik, nanosecond, dan lokasi timezone. time.UTC adalah lokasi bawaan; untuk timezone lokal sistem, gunakan time.Local.
Reference Time: Konvensi Unik Go
Go menggunakan pendekatan yang berbeda dari bahasa lain untuk mendefinisikan format waktu. Alih-alih placeholder seperti YYYY atau MM, Go menggunakan satu reference time yang sudah ditentukan:
Mon Jan 2 15:04:05 MST 2006
Ini bukan waktu sembarang. Setiap komponennya punya nilai yang unik dan tidak bisa tertukar:
| Komponen | Nilai | Keterangan |
|---|---|---|
| Tahun | 2006 | Satu-satunya angka empat digit yang mengandung 6 |
| Bulan (angka) | 01 | Bulan pertama dengan nol di depan |
| Bulan (teks) | Jan / January | Nama bulan ke-1 |
| Hari | 02 | Hari ke-2 |
| Jam (24h) | 15 | Jam 3 sore dalam format 24 jam |
| Jam (12h) | 3 / 03 | Format 12 jam |
| Menit | 04 | Menit ke-4 |
| Detik | 05 | Detik ke-5 |
| Hari dalam seminggu | Mon / Monday | Hari ke-2 dalam seminggu |
Artinya, jika kamu ingin format YYYY-MM-DD, kamu tulis 2006-01-02 — bukan YYYY-MM-DD. Ini membingungkan pertama kali, tapi begitu dipahami, ia tidak pernah ambigu.
Hafalkan reference time sebagai kalimat: “Pada tanggal 2 Januari 2006, pukul 15:04
”. Angka-angka itu adalah1, 2, 3, 4, 5, 6, 7 berurutan jika ditulis 01/02 03:04:05 2006 -07.Formatting: Waktu ke String
Method Format mengubah nilai time.Time menjadi string sesuai layout yang kamu definisikan:
// waktu.go
package main
import (
"fmt"
"time"
)
func main() {
pencatatan := time.Date(2024, time.August, 17, 8, 30, 0, 0, time.UTC)
// format tanggal saja
fmt.Println(pencatatan.Format("2006-01-02"))
// format dengan nama hari dan bulan
fmt.Println(pencatatan.Format("Monday, 02 January 2006"))
// format dengan waktu lengkap
fmt.Println(pencatatan.Format("02 Jan 2006 — 15:04 WIB"))
// menggunakan konstanta bawaan
fmt.Println(pencatatan.Format(time.RFC3339))
}
2024-08-17
Saturday, 17 August 2024
17 Aug 2024 — 08:30 WIB
2024-08-17T08:30:00Z
Go menyediakan konstanta layout yang sudah jadi untuk format standar. Yang paling sering dipakai dalam API dan sistem:
time.RFC3339—"2006-01-02T15:04:05Z07:00"— standar ISO 8601, umum di JSON APItime.RFC1123—"Mon, 02 Jan 2006 15:04:05 MST"— standar HTTP headertime.DateOnly—"2006-01-02"— tanggal saja (tersedia sejak Go 1.20)time.TimeOnly—"15:04:05"— waktu saja (tersedia sejak Go 1.20)
Parsing: String ke Waktu
time.Parse melakukan kebalikan dari Format — mengubah string menjadi time.Time. Layout yang diberikan harus sesuai dengan format string yang di-parse:
// waktu.go
package main
import (
"fmt"
"time"
)
func main() {
// parsing format ISO
tgl1, err := time.Parse("2006-01-02", "2024-06-15")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("parsed:", tgl1)
fmt.Println("hari :", tgl1.Format("Monday"))
// parsing dengan waktu
tgl2, err := time.Parse("02/01/2006 15:04", "17/08/2024 08:30")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("parsed:", tgl2.Format(time.RFC3339))
}
parsed: 2024-06-15 00:00:00 +0000 UTC
hari : Saturday
parsed: 2024-08-17T08:30:00Z
time.Parse selalu mengembalikan dua nilai: time.Time dan error. Jangan abaikan errornya — jika string tidak sesuai layout, nilainya adalah zero time (0001-01-01 00:00:00) dan error akan menjelaskan ketidaksesuaiannya.
time.Parse mengasumsikan timezone UTC jika layout tidak menyertakan informasi timezone. Jika kamu mem-parse string yang mengandung timezone tertentu, sertakan komponen timezone di layout — misalnya "2006-01-02T15:04:05Z07:00" untuk RFC3339.
Operasi Perbandingan dan Selisih
Setelah memiliki nilai time.Time, kamu bisa membandingkan dan menghitung selisih antar waktu:
// waktu.go
package main
import (
"fmt"
"time"
)
func main() {
mulai, _ := time.Parse("2006-01-02", "2024-01-01")
selesai, _ := time.Parse("2006-01-02", "2024-08-17")
durasi := selesai.Sub(mulai)
fmt.Printf("mulai : %s\n", mulai.Format("02 January 2006"))
fmt.Printf("selesai: %s\n", selesai.Format("02 January 2006"))
fmt.Printf("durasi : %.0f hari\n", durasi.Hours()/24)
// perbandingan
if selesai.After(mulai) {
fmt.Println("selesai lebih baru dari mulai")
}
// selisih dari sekarang
sekarang := time.Now()
sejak := time.Since(mulai)
fmt.Printf("sejak 1 Jan 2024: %.0f hari\n", sejak.Hours()/24)
_ = sekarang
}
mulai : 01 January 2024
selesai: 17 August 2024
durasi : 229 hari
selesai lebih baru dari mulai
sejak 1 Jan 2024: 74 hari
Sub mengembalikan time.Duration — tipe yang merepresentasikan selang waktu dalam nanosecond. Method .Hours(), .Minutes(), .Seconds(), dan .Milliseconds() mengonversinya ke satuan yang lebih mudah dibaca. time.Since(t) adalah shorthand untuk time.Now().Sub(t).
time.Duration: Merepresentasikan Selang Waktu
Setiap kali kamu memanggil Sub, time.Since, atau time.Until, hasilnya bertipe time.Duration. Tipe ini bukan sekadar angka — ia adalah int64 yang merepresentasikan durasi dalam satuan nanosecond.
Go mendefinisikan konstanta hierarkis untuk satuan yang lebih manusiawi:
| Konstanta | Nilai |
|---|---|
time.Nanosecond | 1 |
time.Microsecond | 1.000 nanosecond |
time.Millisecond | 1.000 microsecond |
time.Second | 1.000 millisecond |
time.Minute | 60 detik |
time.Hour | 60 menit |
Konstanta-konstanta ini bisa dikombinasikan secara langsung dengan operator aritmatika:
// waktu.go
package main
import (
"fmt"
"time"
)
func main() {
// membuat durasi dari kombinasi konstanta
tenggat := 2*time.Hour + 30*time.Minute + 15*time.Second
fmt.Println("tenggat:", tenggat)
// konversi ke satuan berbeda
fmt.Printf("dalam detik: %.0f\n", tenggat.Seconds())
fmt.Printf("dalam menit: %.1f\n", tenggat.Minutes())
fmt.Printf("dalam jam : %.2f\n", tenggat.Hours())
}
tenggat: 2h30m15s
dalam detik: 9015
dalam menit: 150.2
dalam jam : 2.50
Perhatikan output 2h30m15s — Go secara otomatis memformat Duration ke representasi yang ringkas dan mudah dibaca.
Aritmatika Durasi
time.Duration bisa ditambah, dikurangi, dan dibandingkan seperti angka biasa. Ini membuatnya berguna untuk menghitung selang waktu secara tepat:
// waktu.go
package main
import (
"fmt"
"time"
)
func main() {
mulai := time.Now()
// simulasi pekerjaan yang berlangsung beberapa saat
proses1 := 450 * time.Millisecond
proses2 := 1*time.Second + 200*time.Millisecond
total := proses1 + proses2
fmt.Printf("proses 1: %v\n", proses1)
fmt.Printf("proses 2: %v\n", proses2)
fmt.Printf("total : %v\n", total)
// membandingkan durasi
batas := 2 * time.Second
if total > batas {
fmt.Println("peringatan: total durasi melebihi batas")
} else {
fmt.Printf("masih dalam batas (sisa: %v)\n", batas-total)
}
fmt.Printf("waktu berlalu sejak mulai: %v\n", time.Since(mulai).Round(time.Millisecond))
}
proses 1: 450ms
proses 2: 1.2s
total : 1.65s
masih dalam batas (sisa: 350ms)
waktu berlalu sejak mulai: 0ms
Method .Round(d) membulatkan durasi ke kelipatan terdekat dari d — berguna saat mencetak durasi agar tidak menampilkan presisi nanosecond yang berlebihan.
Gunakan time.Duration untuk semua kalkulasi selang waktu, bukan operasi manual dengan angka mentah. Menulis 5 * time.Second jauh lebih aman dan terbaca dibanding menyimpan 5000 sebagai angka biasa.
Latihan
Latihan 1 — Format kustom:
Buat variabel time.Time yang merepresentasikan hari kemerdekaan Indonesia (17 Agustus 1945, pukul 10
"02 January 2006", "Monday, 02/01/2006", dan time.RFC3339.
Latihan 2 — Hitung usia:
Tulis fungsi hitungUsia(lahir time.Time) int yang mengembalikan usia dalam tahun penuh berdasarkan tanggal lahir yang diberikan. Pastikan fungsi ini benar untuk kasus tanggal ulang tahun yang belum terlewati di tahun berjalan.
Latihan 3 — Validasi input tanggal:
Buat fungsi parseTanggal(input string) (time.Time, error) yang mencoba mem-parse string input dalam tiga format berbeda secara berurutan: "2006-01-02", "02/01/2006", dan "2 January 2006". Kembalikan waktu yang berhasil di-parse, atau error jika tidak ada format yang cocok.
Latihan 4 — Stopwatch sederhana:
Buat fungsi ukurWaktu(fn func()) time.Duration yang mengukur berapa lama fungsi fn dieksekusi. Gunakan fungsi ini untuk mengukur durasi sebuah loop for i := 0; i < 1_000_000; i++ dan cetak hasilnya dalam milidetik.
Kemampuan bekerja dengan waktu sebagai nilai — menyimpan, memformat, mem-parsing, membandingkan, dan menghitung selang waktu — sudah lengkap. Tapi package time menyimpan satu dimensi lagi yang belum kita sentuh: mengeksekusi kode berdasarkan berjalannya waktu. Bukan hanya tahu jam berapa sekarang, tapi membuat program bereaksi setelah 5 detik berlalu, atau menjalankan sesuatu setiap menit. Itu yang akan kita jelajahi di bab berikutnya dengan time.Timer dan time.Ticker.